PROGETTO DATA MINING: Incidenti stradali in UK¶

D'Atri Fulvio 235344¶

Corso Data Mining 2021/2022¶

INDICE¶

  1. Introduzione
  2. Preparazione dei dati
    1. Importazione delle librerie necessarie
    2. Esplorazione del dataset
      1. Accident_Information
      2. Vehicle_Information
    3. Creazione del Test set
  3. Analisi esplorative (extra)
  4. Pre-Processing
    1. Data Cleaning
      1. Eliminazine valori nulli
      2. Eliminazine outliers
      3. Ribilanciamento delle classi
    2. Esplorazione delle features
      1. Esplorazione attributi categorici
      2. Esplorazione attributi numerici
      3. Feature Selection
    3. Data Trasformation
      1. Dimensionality
        1. LocalAuthority(District)
        2. LocalAuthority(Highway)
        3. make
      2. Encoding
        1. Age_Band_of_Driver
        2. Day_of_Week e Time_Interval
        3. Altri attributi categorici
      3. Feature scaling
    4. Pipeline
  5. Model Selection
  6. Post Processing
  7. Valutazione delle performance
  8. Conclusioni

1. Introduzione¶

Il dataset in oggetto proviene dal governo del Regno Unito, che ha raccolto e pubblicato informazioni dettagliate sugli incidenti stradali in tutto il paese dal 2004 al 2017.
Queste informazioni includono, ma non sono limitate a, posizioni geografiche, condizioni meteorologiche, tipo di veicoli, numero di vittime e manovre dei veicoli, rendendo questo un dataset molto interessante e completo per l'analisi e la ricerca.

Il dataset è stato scaricato dal sito Kaggle dal seguente link:
https://www.kaggle.com/datasets/tsiaras/uk-road-safety-accidents-and-vehicles?select=Vehicle_Information.csv.
A sua volta i dati provengono dal sito Web Open Data del governo del Regno Unito, dove sono stati pubblicati dal Dipartimento dei trasporti.

L'obiettivo principale di questo progetto è risolvere il seguente task:

Task: date le informazioni su caratteristiche di un incidente (es. condizioni della strada, condizioni metereologiche, condizioni del guidatore, ecc.), predire la gravità che avrà quell'incidente (Accident_Severity).

La soluzione fornita qui è progettata per essere una soluzione autonoma. Quindi non vi è alcun requisito in merito alla compatibilità o integrazione con altri modelli.

Il modo migliore per affrontare il problema è quello di utilizzare un approccio di apprendimento supervisionato, con qualche algoritmo di classificazione binaria.

Il resto del progetto è suddiviso nei seguenti capitoli:

  • 2. Preparazione dei dati: verranno effettuate le fasi preliminari, quali importazione di librerie utili nel preseguo; importazione del dataset da un formato .csv ad uno compatibile per le manipolazioni che andranno effettuate; ed infine già in questa fase verrà suddiviso il dataset in training set e testing set, in modo da mettere da parte il test set e non toccarlo più se non nel Capitolo 7 Valutazione delle performance, per valutare le performance finale del modello scelto.
  • 3. Analisi esplorative (extra): è un capitolo extra, in cui verrà fatta una data analysis sul dataset in oggetto; ovvero sul dataset originale verrà eseguito un'analisi sugli incidenti avvenuti in Gran Bretagna.
  • 4. Pre-Processing: in questo capitolo sarà affrontata tutta la parte che trasforma il dataset in input in un nuovo dataset che sia "appetibile" per gli algoritmi di data mining. Quindi ci sarà una prima fase di Data Cleaning ad esempio per rimuovere i valori null e gli outliers, oppure per ribilanciare il dataset se si è difronte ad un problema con classi sbilanciate (imbalancing learning); una seconda fase in cui verrà scelto un sottoinsieme delle features (features selection) sia per rendere l'esecuzione degli algoritmi più efficiente e sia per ridurre la varianza e quindi l'overfitting; attraverso tecniche supervisionate oppure grazie all'importanza delle features fornita da classificatori come il RandomForest; infine un'ultima fase dove verranno eseguite tutte le trasformazioni sui dati, quali la trasformazione di attributi categorici in numerici, oppure la standardizzazione dei valori a causa dei problemi di scala. Tutte queste fasi verranno racchiuse in una pipeline, così da creare un pacchetto completo che dato in ingresso un dataset, restituisce il dataset pre-elaborato.
  • 5. Model Selection: in questo capitolo verranno addestrati più modelli di machine learning, e ne verranno valutate le performance attraverso la tecnica K-fold cross validation, in modo da poter confrontarle tra loro e scegliere il migliore classificatore sulla base del miglior score ottenuto su un validation set.
  • 6. Post Processing: in questo capitolo si modificherà un passo in precedenza, ovvero cerrà cambiata la tecnica di undersampling, e si riaddestreranno i classificatori, in modo da trovare il nuovo classificatore migliore, che come si vedrà più avanti si noterà un netto miglioramento rispetto al precedente.
  • 7. Valutazione delle performance: in questo capitolo verranno infine valutate le performance del miglior modello scelto nel capitolo precedente sul test set.
  • 8. Conclusioni: infine verrano riassunti i passi eseguiti, fornendo la pipeline output finale del task in questione.

2. Preparazione dei dati¶

2.1. Importazione delle librerie necessarie¶

Il codice seguente è scritto in Python 3.x. Le librerie forniscono funzionalità per eseguire le attività necessarie.

In [ ]:
#load packages
import sys 
import pickle
print("Python version: {}". format(sys.version))

import pandas as pd 
print("pandas version: {}". format(pd.__version__))
pd.set_option('display.max_columns', None)

import matplotlib 
print("matplotlib version: {}". format(matplotlib.__version__))

import numpy as np 
print("NumPy version: {}". format(np.__version__))

import scipy as sp 
print("SciPy version: {}". format(sp.__version__)) 

import IPython
from IPython import display 
print("IPython version: {}". format(IPython.__version__)) 

import sklearn 
print("scikit-learn version: {}". format(sklearn.__version__))

#misc libraries
import random
import time
import calendar

#ignore warnings
import warnings
warnings.filterwarnings('ignore')
print('-'*25)

#Imbalanced learning
from imblearn.under_sampling import RandomUnderSampler
from imblearn.under_sampling import NearMiss

#Transformer
from sklearn.pipeline import Pipeline
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.preprocessing import FunctionTransformer
from sklearn.cluster import KMeans
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OrdinalEncoder
from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import RobustScaler
from collections import Counter


#Common Model Algorithms
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import BernoulliNB
from sklearn.naive_bayes import GaussianNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.svm import SVC
from sklearn.linear_model import SGDClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.ensemble import AdaBoostClassifier
#import xgboost as xgb
#from xgboost import XGBClassifier
#from sklearn import svm, tree, linear_model, neighbors, naive_bayes, ensemble, discriminant_analysis, gaussian_process

#Common Model Helpers
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder, LabelEncoder, OrdinalEncoder
from sklearn.model_selection import GridSearchCV

#Visualization
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.pylab as pylab
import seaborn as sns
from itertools import cycle
#from pandas.tools.plotting import scatter_matrix

#Configure Visualization Defaults
#%matplotlib inline = show plots in Jupyter Notebook browser
%matplotlib inline
mpl.style.use('ggplot')
sns.set_style('white')
pylab.rcParams['figure.figsize'] = 12,8

#check cartelle
from subprocess import check_output
print(check_output(["ls", "./input"]).decode("utf8"))
print(check_output(["ls", "./img"]).decode("utf8"))
Python version: 3.9.12 (main, Apr  5 2022, 06:56:58) 
[GCC 7.5.0]
pandas version: 1.4.2
matplotlib version: 3.5.1
NumPy version: 1.21.5
SciPy version: 1.7.3
IPython version: 8.2.0
scikit-learn version: 1.0.2
-------------------------
Accident_Information.csv
Vehicle_Information.csv

carcrash.jpg
features_importance.png
nearmiss.png
PROGETTO.ipynb
prova.png
uk.png

2.2. Esplorazione del dataset¶

Il dataset è composto da due file CSV:

  • AccidentInformation.csv: ogni riga del file rappresenta un unico incidente stradale (identificato dalla colonna AccidentIndex), caratterizzato da varie proprietà relative al singolo incidente (34 colonne).
  • Vehicle_Information.csv: ogni riga nel file rappresenta il coinvolgimento di un veicolo unico in un incidente stradale unico, con varie proprietà di veicoli e passeggeri come colonne (24 colonne).

I due file/set di dati sopra menzionati possono essere collegati tramite l'identificatore univoco dell'incidente stradale (colonna Accident_Index).

Più in dettaglio ecco spiegato brevemente il significato degli attributi dei due dataset:

Per quanto riguarda AccidentInformation.csv:

  1. Accident_Index: chiave che identifica un singolo incidente.
  2. 1st_Road_Class: serve per identificare una strada in Gran Bretagna (insieme agli altri 3 attributi).
  3. 1st_Road_Number: serve per identificare una strada in Gran Bretagna (insieme agli altri 3 attributi).
  4. 2nd_Road_Class: serve per identificare una strada in Gran Bretagna (insieme agli altri 3 attributi).
  5. 2nd_Road_Number: serve per identificare una strada in Gran Bretagna (insieme agli altri 3 attributi).
  6. Accident_Severity: la gravità dell'incidente (Fatal, Severious, Slight). (*)
  7. Carriageway_Hazards: pericoli presenti sulla strada (es. animali, pedoni, oggetti, ecc.).
  8. Date: giorno in cui è avvenuto l'incidente.
  9. Day_of_Week: giorno della settimana in cui è avvenuto l'incidente.
  10. Did_Police_Officer_Attend_Scene_of_Accident:
  11. Junction_Control: nel caso di incidente nei pressi di un incrocio, come era controllato quest'ultimo (es. semaforo, segnale di stop, vigile, ecc.).
  12. Junction_Detail: nel caso di incidente nei pressi di un incrocio, come era l'incrocio (es. incroicio a T, rotatoia, ecc.).
  13. Latitude: latitudine geografica in cui è avvenuto l'incidente.
  14. Light_Conditions: Condizioni di luce quando è avvenuto l'incidente.
  15. Local_Authority_(District): ente locale del distretto.
  16. Local_Authority_(Highway): ente locale dell'autostrada.
  17. Location_Easting_OSGR: altra tipologia di coordinate spaziali.
  18. Location_Northing_OSGR: altra tipologia di coordinate spaziali.
  19. Longitude: longitudine geografica in cui è avvenuto l'incidente.
  20. LSOA_of_Accident_Location: leaving scene of an accident.
  21. Number_of_Casualties: numero di morti nell'incidente.
  22. Number_of_Vehicles: numero di veicoli coinvolti nell'incidente.
  23. Pedestrian_Crossing-Human_Control: controllo umano dell'attraversamento pedonale.
  24. Pedestrian_Crossing-Physical_Facilities: attraversamento pedonale strutture fisiche.
  25. Police_Force: polizia intervenuta nell'incidente.
  26. Road_Surface_Conditions: condizioni stradali nel momento dell'incidente (es. bagnato, innevato, ecc.).
  27. Road_Type: tipo di strada (es. carreggiata singola, doppia, ecc.).
  28. Special_Conditions_at_Site: condizioni anomale presenti sulla strada (es. lavori, benzina, segnali non funzionanti, ecc.).
  29. Speed_limit: limite di velocità presente nella strada in cui è avvenuto l'incidente.
  30. Time: orario in cui è avvenuto l'incidente.
  31. Urban_or_Rural_Area: area urbana o rurale.
  32. Weather_Conditions: condizioni atmosferiche quando è avvenuto l'incidente (es. pioggia, vento, nebbia, ecc.).
  33. Year: anno in cui è avvenuto l'incidente.
  34. InScotland: se l'incidente è avvenuto in Scozia o no.

Per quanto riguarda Vehicle_Information.csv:

  1. Accident_Index: chiave esterna che associa il singolo veicolo al singolo incidente identificato da questa chiave.
  2. Age_Band_of_Driver: intervalli di età del conducente.
  3. Age_of_Vehicle: età del veicolo.
  4. Driver_Home_Area_Type: l'area se rurale o urbana della casa del guidatore.
  5. Driver_IMD_Decile: Multiple Deprivation Index, è un indice che indica un livello di povertà del guidatore.
  6. Engine_Capacity_.CC.: cavalli del motore dell'auto.
  7. Hit_Object_in_Carriageway: oggetti colpiti nell'incidente presenti sulla strada.
  8. Hit_Object_off_Carriageway: oggetti colpiti nell'incidente presenti fuori la strada.
  9. Journey_Purpose_of_Driver: obiettivo che aveva il viaggio per il guidatore.
  10. Junction_Location: se l'incidente è avvenuto in prossimità di un incrocio, cosa stava facendo.
  11. make: marca dell'auto.
  12. model: modello dell'auto.
  13. Propulsion_Code: tipo di carburante dell'auto.
  14. Sex_of_Driver: sesso del guidatore.
  15. Skidding_and_Overturning: se l'auto si è ribaltata nell'incidente o slittata.
  16. Towing_and_Articulation: caratteristiche del traino se presente durante l'incidente.
  17. Vehicle_Leaving_Carriageway: come è uscito il veicolo dalla carreggiata prima dell'incidente.
  18. Vehicle_Location.Restricted_Lane: numero corsia della strada.
  19. Vehicle_Manoeuvre: manovra che ha compiuto il veicoli prima dell'incidente.
  20. Vehicle_Reference:
  21. Vehicle_Type: tipo di veicolo.
  22. Was_Vehicle_Left_Hand_Drive: se il veicolo aveva la guida a sinistra.
  23. X1st_Point_of_Impact: primo parte del veicoli che ha subito l'impatto.
  24. Year: anno in cui è avvenuto l'incidente.

(*) Questo è l'attributo che si vuole predire

Si importano i due dataset con pandas: Accident_Information e Vehicle_information.

2.2.1. Accident_Information:¶

In [ ]:
accidents = pd.read_csv('./input/Accident_Information.csv')
print('Records:', accidents.shape[0], '\nColumns:', accidents.shape[1])
accidents.head()
Records: 2047256 
Columns: 34
Out[ ]:
Accident_Index 1st_Road_Class 1st_Road_Number 2nd_Road_Class 2nd_Road_Number Accident_Severity Carriageway_Hazards Date Day_of_Week Did_Police_Officer_Attend_Scene_of_Accident Junction_Control Junction_Detail Latitude Light_Conditions Local_Authority_(District) Local_Authority_(Highway) Location_Easting_OSGR Location_Northing_OSGR Longitude LSOA_of_Accident_Location Number_of_Casualties Number_of_Vehicles Pedestrian_Crossing-Human_Control Pedestrian_Crossing-Physical_Facilities Police_Force Road_Surface_Conditions Road_Type Special_Conditions_at_Site Speed_limit Time Urban_or_Rural_Area Weather_Conditions Year InScotland
0 200501BS00001 A 3218.0 NaN 0.0 Serious None 2005-01-04 Tuesday 1.0 Data missing or out of range Not at junction or within 20 metres 51.489096 Daylight Kensington and Chelsea Kensington and Chelsea 525680.0 178240.0 -0.191170 E01002849 1 1 0.0 1.0 Metropolitan Police Wet or damp Single carriageway None 30.0 17:42 Urban Raining no high winds 2005 No
1 200501BS00002 B 450.0 C 0.0 Slight None 2005-01-05 Wednesday 1.0 Auto traffic signal Crossroads 51.520075 Darkness - lights lit Kensington and Chelsea Kensington and Chelsea 524170.0 181650.0 -0.211708 E01002909 1 1 0.0 5.0 Metropolitan Police Dry Dual carriageway None 30.0 17:36 Urban Fine no high winds 2005 No
2 200501BS00003 C 0.0 NaN 0.0 Slight None 2005-01-06 Thursday 1.0 Data missing or out of range Not at junction or within 20 metres 51.525301 Darkness - lights lit Kensington and Chelsea Kensington and Chelsea 524520.0 182240.0 -0.206458 E01002857 1 2 0.0 0.0 Metropolitan Police Dry Single carriageway None 30.0 00:15 Urban Fine no high winds 2005 No
3 200501BS00004 A 3220.0 NaN 0.0 Slight None 2005-01-07 Friday 1.0 Data missing or out of range Not at junction or within 20 metres 51.482442 Daylight Kensington and Chelsea Kensington and Chelsea 526900.0 177530.0 -0.173862 E01002840 1 1 0.0 0.0 Metropolitan Police Dry Single carriageway None 30.0 10:35 Urban Fine no high winds 2005 No
4 200501BS00005 Unclassified 0.0 NaN 0.0 Slight None 2005-01-10 Monday 1.0 Data missing or out of range Not at junction or within 20 metres 51.495752 Darkness - lighting unknown Kensington and Chelsea Kensington and Chelsea 528060.0 179040.0 -0.156618 E01002863 1 1 0.0 0.0 Metropolitan Police Wet or damp Single carriageway None 30.0 21:13 Urban Fine no high winds 2005 No
In [ ]:
accidents.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2047256 entries, 0 to 2047255
Data columns (total 34 columns):
 #   Column                                       Dtype  
---  ------                                       -----  
 0   Accident_Index                               object 
 1   1st_Road_Class                               object 
 2   1st_Road_Number                              float64
 3   2nd_Road_Class                               object 
 4   2nd_Road_Number                              float64
 5   Accident_Severity                            object 
 6   Carriageway_Hazards                          object 
 7   Date                                         object 
 8   Day_of_Week                                  object 
 9   Did_Police_Officer_Attend_Scene_of_Accident  float64
 10  Junction_Control                             object 
 11  Junction_Detail                              object 
 12  Latitude                                     float64
 13  Light_Conditions                             object 
 14  Local_Authority_(District)                   object 
 15  Local_Authority_(Highway)                    object 
 16  Location_Easting_OSGR                        float64
 17  Location_Northing_OSGR                       float64
 18  Longitude                                    float64
 19  LSOA_of_Accident_Location                    object 
 20  Number_of_Casualties                         int64  
 21  Number_of_Vehicles                           int64  
 22  Pedestrian_Crossing-Human_Control            float64
 23  Pedestrian_Crossing-Physical_Facilities      float64
 24  Police_Force                                 object 
 25  Road_Surface_Conditions                      object 
 26  Road_Type                                    object 
 27  Special_Conditions_at_Site                   object 
 28  Speed_limit                                  float64
 29  Time                                         object 
 30  Urban_or_Rural_Area                          object 
 31  Weather_Conditions                           object 
 32  Year                                         int64  
 33  InScotland                                   object 
dtypes: float64(10), int64(3), object(21)
memory usage: 531.1+ MB
In [ ]:
plt.figure(figsize=(15,5))
s = pd.Series(accidents.isnull().sum()/accidents.shape[0]).sort_values(ascending=False)
sns.barplot(x = s.index, y = s.values)
plt.xticks(rotation=90)
plt.title("Valori nulli nelle features in percentuale", size=20)
Out[ ]:
Text(0.5, 1.0, 'Valori nulli nelle features in percentuale')

Si nota come la maggior parte dei valori sono di tipo object, ovvero categorici; inoltre in questo grafico a barre sono evidenziate le features che in percentuale rispetto al numero totale di righe, hanno un numero considerevole di valori null.

2.2.2. Vehicle_Information:¶

In [ ]:
vehicles = pd.read_csv('./input/Vehicle_Information.csv', encoding='ISO-8859-1')
print('Records:', vehicles.shape[0], '\nColumns:', vehicles.shape[1])
vehicles.head()
Records: 2177205 
Columns: 24
Out[ ]:
Accident_Index Age_Band_of_Driver Age_of_Vehicle Driver_Home_Area_Type Driver_IMD_Decile Engine_Capacity_.CC. Hit_Object_in_Carriageway Hit_Object_off_Carriageway Journey_Purpose_of_Driver Junction_Location make model Propulsion_Code Sex_of_Driver Skidding_and_Overturning Towing_and_Articulation Vehicle_Leaving_Carriageway Vehicle_Location.Restricted_Lane Vehicle_Manoeuvre Vehicle_Reference Vehicle_Type Was_Vehicle_Left_Hand_Drive X1st_Point_of_Impact Year
0 200401BS00001 26 - 35 3.0 Urban area 4.0 1588.0 None None Data missing or out of range Data missing or out of range ROVER 45 CLASSIC 16V Petrol Male None No tow/articulation Did not leave carriageway 0.0 Going ahead other 2 109 Data missing or out of range Front 2004
1 200401BS00002 26 - 35 NaN Urban area 3.0 NaN None None Data missing or out of range Data missing or out of range BMW C1 NaN Male None No tow/articulation Did not leave carriageway 0.0 Going ahead other 1 109 Data missing or out of range Front 2004
2 200401BS00003 26 - 35 4.0 Data missing or out of range NaN 998.0 None None Data missing or out of range Data missing or out of range NISSAN MICRA CELEBRATION 16V Petrol Male None No tow/articulation Did not leave carriageway 0.0 Turning right 1 109 Data missing or out of range Front 2004
3 200401BS00003 66 - 75 NaN Data missing or out of range NaN NaN None None Data missing or out of range Data missing or out of range LONDON TAXIS INT TXII GOLD AUTO NaN Male None No tow/articulation Did not leave carriageway 0.0 Going ahead other 2 109 Data missing or out of range Front 2004
4 200401BS00004 26 - 35 1.0 Urban area 4.0 124.0 None None Data missing or out of range Data missing or out of range PIAGGIO VESPA ET4 Petrol Male None No tow/articulation Did not leave carriageway 0.0 Going ahead other 1 Motorcycle 125cc and under Data missing or out of range Front 2004
In [ ]:
vehicles.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2177205 entries, 0 to 2177204
Data columns (total 24 columns):
 #   Column                            Dtype  
---  ------                            -----  
 0   Accident_Index                    object 
 1   Age_Band_of_Driver                object 
 2   Age_of_Vehicle                    float64
 3   Driver_Home_Area_Type             object 
 4   Driver_IMD_Decile                 float64
 5   Engine_Capacity_.CC.              float64
 6   Hit_Object_in_Carriageway         object 
 7   Hit_Object_off_Carriageway        object 
 8   Journey_Purpose_of_Driver         object 
 9   Junction_Location                 object 
 10  make                              object 
 11  model                             object 
 12  Propulsion_Code                   object 
 13  Sex_of_Driver                     object 
 14  Skidding_and_Overturning          object 
 15  Towing_and_Articulation           object 
 16  Vehicle_Leaving_Carriageway       object 
 17  Vehicle_Location.Restricted_Lane  float64
 18  Vehicle_Manoeuvre                 object 
 19  Vehicle_Reference                 int64  
 20  Vehicle_Type                      object 
 21  Was_Vehicle_Left_Hand_Drive       object 
 22  X1st_Point_of_Impact              object 
 23  Year                              int64  
dtypes: float64(4), int64(2), object(18)
memory usage: 398.7+ MB
In [ ]:
plt.figure(figsize=(15,5))
s = pd.Series(vehicles.isnull().sum()/vehicles.shape[0]).sort_values(ascending=False)
sns.barplot(x = s.index, y = s.values)
plt.xticks(rotation=90)
plt.title("Valori nulli nelle features in percentuale", size=20)
Out[ ]:
Text(0.5, 1.0, 'Valori nulli nelle features in percentuale')

Si nota come la maggior parte dei valori sono di tipo object, inoltre in questo grafico a barre sono evidenziate le features che in percentuale rispetto al numero totale di righe, hanno un numero considerevole di valori null.

Si visualizzano alcune informazioni sulla distribuzione delle features:

In [ ]:
accidents.describe().T
Out[ ]:
count mean std min 25% 50% 75% max
1st_Road_Number 2047254.0 992.105099 1809.407936 0.000000 0.000000 118.000000 702.000000 9.999000e+03
2nd_Road_Number 2029663.0 372.815342 1287.796383 0.000000 0.000000 0.000000 0.000000 9.999000e+03
Did_Police_Officer_Attend_Scene_of_Accident 2046978.0 1.202319 0.408194 1.000000 1.000000 1.000000 1.000000 3.000000e+00
Latitude 2047082.0 52.559780 1.445506 49.912941 51.485404 52.237583 53.455902 6.075754e+01
Location_Easting_OSGR 2047092.0 441446.210036 95496.198208 64950.000000 378063.500000 443050.000000 524298.250000 6.555400e+05
Location_Northing_OSGR 2047092.0 296885.465668 160527.257527 10290.000000 177756.750000 261183.500000 395610.000000 1.208800e+06
Longitude 2047081.0 -1.410155 1.403532 -7.516225 -2.329610 -1.362233 -0.205260 1.762010e+00
Number_of_Casualties 2047256.0 1.345843 0.817963 1.000000 1.000000 1.000000 1.000000 9.300000e+01
Number_of_Vehicles 2047256.0 1.833525 0.715054 1.000000 1.000000 2.000000 2.000000 6.700000e+01
Pedestrian_Crossing-Human_Control 2044336.0 0.010417 0.135113 0.000000 0.000000 0.000000 0.000000 2.000000e+00
Pedestrian_Crossing-Physical_Facilities 2043696.0 0.751802 1.835289 0.000000 0.000000 0.000000 0.000000 8.000000e+00
Speed_limit 2047219.0 38.843597 14.147910 0.000000 30.000000 30.000000 50.000000 7.000000e+01
Year 2047256.0 2010.523806 3.765624 2005.000000 2007.000000 2010.000000 2014.000000 2.017000e+03
In [ ]:
vehicles.describe().T
Out[ ]:
count mean std min 25% 50% 75% max
Age_of_Vehicle 1819056.0 7.108167 4.725886 1.0 3.0 7.0 10.0 111.0
Driver_IMD_Decile 1442393.0 5.387559 2.821651 1.0 3.0 5.0 8.0 10.0
Engine_Capacity_.CC. 1911344.0 2042.233961 1950.143170 1.0 1299.0 1598.0 1997.0 96000.0
Vehicle_Location.Restricted_Lane 2175888.0 0.107304 0.879964 0.0 0.0 0.0 0.0 9.0
Vehicle_Reference 2177205.0 1.553405 0.775248 1.0 1.0 1.0 2.0 91.0
Year 2177205.0 2010.934147 3.694375 2004.0 2008.0 2011.0 2014.0 2016.0
In [ ]:
accidents.hist(bins=50, figsize=(20,15))
plt.show()

Già qui si possono fare alcune considerazioni, si nota come Longitude e Location_Easting_OSGR hanno circa la stessa distribuzione (molto correlati), così vale anche per Latitude e Location_Northing_OSGR. Questo perchè effettivamente sono due modi diversi di localizzare l'incidente, quindi alla fine hanno lo stesso scopo e perciò per la fase di ML verrà tenuta una solo delle due. Dagli altri grafici altre considerazioni possono essere che la maggior parte degli incidenti ha 0 morti, oppure che la maggior parte degli incidenti sono avvenuti in strade con limiti di velocità pari a 30 mph, ovvero 48 Km/h; quindi ci fa capire che sono avvenuti non in superstrade, a causa della presenza di più veicoli e caos nelle città. Inoltre si può vedere come il numero di incidenti totali sia sceso negli anni.

In [ ]:
vehicles.hist(bins=50, figsize=(20,15))
plt.show()

In questo caso ad esempio si vede come negli anni il numero di veicoli coinvolti negli incidenti è aumentato.

2.3. Creazione del Test set¶

Prima di dividere da subito il training set dal test set, abbiamo necessità di creare un solo dataset. Quindi effettuiamo una inner join tra i due dataset (accidents e vehicles), in modo tale da aumentare la granularità dei nostri dati, quindi avere non più un insieme di incidenti ma un insieme di veicoli coinvolti in incidenti (ad esempio possono esserci più veicoli coinvolti nello stesso incidente). In questo modo avremo più proprietà interessanti ovvero quelle relative ai veicoli.

In [ ]:
print('Accidents:', accidents.shape)
print('Vehicles:', vehicles.shape)
dataset = pd.merge(vehicles, accidents, how="inner")
print('Dataset:', dataset.shape)
dataset.head()
Accidents: (2047256, 34)
Vehicles: (2177205, 24)
Dataset: (2058408, 56)
Out[ ]:
Accident_Index Age_Band_of_Driver Age_of_Vehicle Driver_Home_Area_Type Driver_IMD_Decile Engine_Capacity_.CC. Hit_Object_in_Carriageway Hit_Object_off_Carriageway Journey_Purpose_of_Driver Junction_Location make model Propulsion_Code Sex_of_Driver Skidding_and_Overturning Towing_and_Articulation Vehicle_Leaving_Carriageway Vehicle_Location.Restricted_Lane Vehicle_Manoeuvre Vehicle_Reference Vehicle_Type Was_Vehicle_Left_Hand_Drive X1st_Point_of_Impact Year 1st_Road_Class 1st_Road_Number 2nd_Road_Class 2nd_Road_Number Accident_Severity Carriageway_Hazards Date Day_of_Week Did_Police_Officer_Attend_Scene_of_Accident Junction_Control Junction_Detail Latitude Light_Conditions Local_Authority_(District) Local_Authority_(Highway) Location_Easting_OSGR Location_Northing_OSGR Longitude LSOA_of_Accident_Location Number_of_Casualties Number_of_Vehicles Pedestrian_Crossing-Human_Control Pedestrian_Crossing-Physical_Facilities Police_Force Road_Surface_Conditions Road_Type Special_Conditions_at_Site Speed_limit Time Urban_or_Rural_Area Weather_Conditions InScotland
0 200501BS00002 36 - 45 3.0 Data missing or out of range NaN 8268.0 None None Journey as part of work Leaving roundabout DENNIS NaN Heavy oil Male None No tow/articulation Did not leave carriageway 0.0 Slowing or stopping 1 Bus or coach (17 or more pass seats) No Nearside 2005 B 450.0 C 0.0 Slight None 2005-01-05 Wednesday 1.0 Auto traffic signal Crossroads 51.520075 Darkness - lights lit Kensington and Chelsea Kensington and Chelsea 524170.0 181650.0 -0.211708 E01002909 1 1 0.0 5.0 Metropolitan Police Dry Dual carriageway None 30.0 17:36 Urban Fine no high winds No
1 200501BS00003 26 - 35 5.0 Urban area 3.0 8300.0 Parked vehicle None Journey as part of work Not at or within 20 metres of junction DENNIS NaN Heavy oil Male None No tow/articulation Did not leave carriageway 0.0 Going ahead right-hand bend 1 Bus or coach (17 or more pass seats) No Nearside 2005 C 0.0 NaN 0.0 Slight None 2005-01-06 Thursday 1.0 Data missing or out of range Not at junction or within 20 metres 51.525301 Darkness - lights lit Kensington and Chelsea Kensington and Chelsea 524520.0 182240.0 -0.206458 E01002857 1 2 0.0 0.0 Metropolitan Police Dry Single carriageway None 30.0 00:15 Urban Fine no high winds No
2 200501BS00004 46 - 55 4.0 Urban area 1.0 1769.0 None None Other/Not known (2005-10) Not at or within 20 metres of junction NISSAN ALMERA SE AUTO Petrol Female None No tow/articulation Did not leave carriageway 0.0 Going ahead other 1 Car No Front 2005 A 3220.0 NaN 0.0 Slight None 2005-01-07 Friday 1.0 Data missing or out of range Not at junction or within 20 metres 51.482442 Daylight Kensington and Chelsea Kensington and Chelsea 526900.0 177530.0 -0.173862 E01002840 1 1 0.0 0.0 Metropolitan Police Dry Single carriageway None 30.0 10:35 Urban Fine no high winds No
3 200501BS00005 46 - 55 10.0 Data missing or out of range NaN 85.0 Kerb None Other/Not known (2005-10) Not at or within 20 metres of junction HONDA NaN Petrol Male Skidded No tow/articulation Did not leave carriageway 0.0 Going ahead other 1 Motorcycle 125cc and under No Front 2005 Unclassified 0.0 NaN 0.0 Slight None 2005-01-10 Monday 1.0 Data missing or out of range Not at junction or within 20 metres 51.495752 Darkness - lighting unknown Kensington and Chelsea Kensington and Chelsea 528060.0 179040.0 -0.156618 E01002863 1 1 0.0 0.0 Metropolitan Police Wet or damp Single carriageway None 30.0 21:13 Urban Fine no high winds No
4 200501BS00006 46 - 55 1.0 Urban area 4.0 2976.0 None None Other/Not known (2005-10) Not at or within 20 metres of junction AUDI A4 SPORT CABRIOLET AUTO Petrol Male None No tow/articulation Did not leave carriageway 0.0 Moving off 1 Car No Did not impact 2005 Unclassified 0.0 NaN 0.0 Slight None 2005-01-11 Tuesday 1.0 Data missing or out of range Not at junction or within 20 metres 51.515540 Daylight Kensington and Chelsea Kensington and Chelsea 524770.0 181160.0 -0.203238 E01002832 1 2 0.0 0.0 Metropolitan Police Wet or damp Single carriageway Oil or diesel 30.0 12:40 Urban Raining no high winds No

Notiamo come alcuni veicoli erano riferiti ad incidenti non presenti nel dataset degli incidenti, perchè il nuovo dataset appena creato contiene 2058408 righe al posto di contenere tutte le righe del dataset dei veicoli (2177205).
Inoltre notiamo che il numero di colonne totali del nuovo dataset è pari a 56 invece di 58 (somma colonne dataset incidenti e veicoli) perchè le colonne Accident_Index e Year sono state prese una sola volta.

A questo punto la prima cosa da fare è separare una porzione di dati di questo data set per andare a creare il test set finale. Mentre la restante parte farà parte del training set. Effettuiamo già adesso la divisione per evitare qualsiasi fenomeno di data snooping.
La scelta del test_size è pari a 0.01; questa scelta anche se può sembrare troppo piccola, in realtà è necessaria perchè più avanti la dimensione del training set attraverso tutta la fase di preprocessing diminuirà drasticamente, quindi serviranno in questo momento più tuple nel training set.

In [ ]:
train_set, test_set = train_test_split(dataset, test_size=0.01, random_state=42)
train_set.shape, test_set.shape
Out[ ]:
((2037823, 56), (20585, 56))
In [ ]:
train_set.Accident_Severity.hist(figsize=(7,5))
Out[ ]:
<AxesSubplot:>
In [ ]:
test_set.Accident_Severity.hist(figsize=(7,5))
Out[ ]:
<AxesSubplot:>

Si nota come le distribuzioni non sono variate, come quella per la classe target Accident_Severity.

3. Analisi esplorative (extra)¶

In questo capitolo verranno effettuate alcune analisi esplorative sui dataset, per acquisire ulteriori conoscenze su questi dati, e quindi sugli incidenti avvenuti. Tale fase ricade più in un task di data analysis, per tale motivo si può dire che è una parte extra del progetto. Le analisi saranno effettuate su due dataset inziali (accients e vehicles).

Innanzitutto grazie agli attributi Longitude e Latitude si è in grado di geolocalizzare gli incidenti e quindi vedere la loro posizione sulla mappa.
Da notare che sono presenti molti incidenti (raccolti in 13 anni), che se vengono graficati tutti, si noterebbe ad occhio la cartina geografica della Gran Bretagna!
Una prima analisi può essere una visualizzazione della distribuzione degli incidenti nello spazio, grazie alle coordinate spaziali di ogni singolo incidente, si visualizzerà la posizione di ogni incidente:

In [ ]:
accidents.plot(kind="scatter", x="Longitude", y="Latitude", alpha=0.4,
    s=0.5,
    figsize=(8,12),
    sharex=False)
Out[ ]:
<AxesSubplot:xlabel='Longitude', ylabel='Latitude'>

Si nota come i più di 2 milioni di incidenti sono tanti, infatti l'insieme di tutti gli incidenti ricostruiscono proprio la forma della Gran Bretagna!
Per essere più precisi e avere un migliore senso dell'orientamento si plottano i punti sopra la cartina geografica della Gran Bretagna:

In [ ]:
ac2005 = accidents.loc[accidents["Year"] == 2005]
a, b, c, d = ac2005.Longitude.min(), ac2005.Longitude.max(), ac2005.Latitude.min(), ac2005.Latitude.max()

mymap = plt.imread("./img/uk.png")
box = [a+0.1,b+0.1, c, d-0.05]
plt.figure(figsize=(8, 12))
plt.scatter(
    accidents.Longitude, accidents.Latitude, 
    alpha=0.5, 
    s=0.5,
    c="#1f77b4"
)
plt.imshow(mymap, alpha=1, zorder=0, extent=box)
Out[ ]:
<matplotlib.image.AxesImage at 0x7fbb8c37c8e0>

L'immagine di background inserita corrisponde ad una cartina geografica del Regno Unito in cui sono messe in risalto le strade principali (soprattutto le autostrade); infatti si vede come i punti sono distribuiti lungo di esse (verso la Scozia (nord) si nota di più, perchè nel sud ci sono molti più incidenti visto la presenza di città metropolitane come Londra, Manchester, Birminghan, ecc.)

Come è variato il numero di incidenti negli anni?

In [ ]:
df_byYear = pd.DataFrame(accidents.Year.value_counts().sort_index()).reset_index()
df_byYear.columns = ["Year", "Numero di incidenti"]

plot = sns.barplot(x="Year", y="Numero di incidenti", palette="rocket", data=df_byYear)
plot.get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plot.set_title("Numero di incidenti per ogni anno dal 2005 al 2016")

percentage = np.array(df_byYear[["Numero di incidenti"]]/accidents.shape[0] * 100)
patches = plot.patches

for i in range(len(patches)):
    x = patches[i].get_x() + patches[i].get_width()/2
    y = patches[i].get_height()+300
    plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)

Negli anni il numero di incidenti è diminuito; in particolare l'anno 2005 è stato l'anno con più incidenti, mentre il 2017 con meno incidenti.
Segue la posizione degli incidenti sulla mappa dei due anni (2005 e 2017):

In [ ]:
righe=1
colonne=2
f = plt.figure(figsize=(6*colonne,9*righe))
anno = 2005

for i in range(0, righe):
    for j in range(0, colonne):
        acc_Y = accidents.loc[accidents["Year"]==anno]
        ax = plt.subplot2grid((righe, colonne), (i,j))
        l = "Year = " + str(anno)
        ax.scatter(
            acc_Y.Longitude, acc_Y.Latitude, 
            alpha=0.5, 
            s=1, 
            label = l,
            c= "#1f77b4"
        )
        ax.legend(markerscale=10)
        anno += 12
plt.show()

Si nota come nell'anno 2005 sono presenti molti più punti rispetto il 2017.

In [ ]:
f = plt.figure(figsize=(8,12))

plt.scatter(
    accidents.Longitude, accidents.Latitude, 
    alpha=0.5, 
    s=0.05, 
    label = "Year = 2015",
    c= "#1f77b4"
)
#Londra -> 51.513989036923775, -0.12972395492792754
#Birminghan -> 52.54076987914765, -1.8327262157805437
#Manchester -> 53.55411409487268, -2.2133829968685537
plt.annotate('Londra ', xy=(-0.129, 51.513), xytext=(-0.5, 50), xycoords='data',
    arrowprops=dict(facecolor='red', shrink=0.05),
)
plt.annotate('Birminghan ', xy=(-1.83, 52.54), xytext=(0.58, 53.5), xycoords='data',
    arrowprops=dict(facecolor='red', shrink=0.05),
)
plt.annotate('Manchester ', xy=(-2.21, 53.55), xytext=(-4.97, 54), xycoords='data',
    arrowprops=dict(facecolor='red', shrink=0.05),
)
plt.legend(markerscale=10)
plt.show()

Sono state evidenziate le posizioni di alcune città importanti quali Londra, Manchester e Birminghan; e come si può vedere in queste posizioni (soprattutto Londra), gli incidenti sono molto più densi; dovuti proprio dal fatto che nelle città ci sono tantissimi veicoli in circolazione.

Si possono continuare le analisi spaziali identificando le autorità che sono intervenute in quei particolari incidenti, ovvero le contee in cui sono avvenuti gli incidenti. Queste si dividono per district e highway; di seguito sono mostrati i grafici delle prime 20 contee con maggiori incidenti per ciascuna delle due categorie; inoltre verranno evidenziati anche la loro posizione sulla mappa con diversi colori in modo tale da riconoscere effettivamente l'area di competenza della particolare contea.

In [ ]:
f = plt.figure(figsize=(15,8))

df_byLAD = pd.DataFrame(accidents["Local_Authority_(District)"].value_counts().sort_index()).reset_index()
df_byLAD.columns = ["Local_Authority_(District)", "Numero di incidenti"]
df_byLAD = df_byLAD.sort_values(ascending=False, by=["Numero di incidenti"])[0:20]

plot = sns.barplot(x="Numero di incidenti", y="Local_Authority_(District)", palette="rocket", data=df_byLAD)
plot.get_xaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plot.set_title("Numero di incidenti per autorità locale di distretto")

percentage = np.array(df_byLAD[["Numero di incidenti"]]/accidents.shape[0] * 100)
patches = plot.patches

for i in range(len(patches)):
    y = patches[i].get_y() + patches[i].get_height()/1.5
    x = patches[i].get_width()+1000
    plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)
In [ ]:
f = plt.figure(figsize=(15,8))

df_byLA = pd.DataFrame(accidents["Local_Authority_(Highway)"].value_counts().sort_index()).reset_index()
df_byLA.columns = ["Local_Authority_(Highway)", "Numero di incidenti"]
df_byLA = df_byLA.sort_values(ascending=False, by=["Numero di incidenti"])[0:20]

plot = sns.barplot(x="Numero di incidenti", y="Local_Authority_(Highway)", palette="rocket", data=df_byLA)
plot.get_xaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plot.set_title("Numero di incidenti per autorità locale di autostrada")

percentage = np.array(df_byLA[["Numero di incidenti"]]/accidents.shape[0] * 100)
patches = plot.patches

for i in range(len(patches)):
    y = patches[i].get_y() + patches[i].get_height()/1.5
    x = patches[i].get_width()+1400
    plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)
In [ ]:
f = plt.figure(figsize=(16,12))

colors = [
    "#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#000000", 
    "#800000", "#008000", "#000080", "#808000", "#800080", "#008080", "#808080", 
    "#C00000", "#00C000", "#0000C0", "#C0C000", "#C000C0", "#00C0C0", "#C0C0C0", 
    "#400000", "#004000", "#000040", "#404000", "#400040", "#004040", "#404040", 
    "#200000", "#002000", "#000020", "#202000", "#200020", "#002020", "#202020", 
    "#600000", "#006000", "#000060", "#606000", "#600060", "#006060", "#606060", 
    "#A00000", "#00A000", "#0000A0", "#A0A000", "#A000A0", "#00A0A0", "#A0A0A0", 
    "#E00000", "#00E000", "#0000E0", "#E0E000", "#E000E0", "#00E0E0", "#E0E0E0", 
]

localAuth = df_byLAD["Local_Authority_(District)"].values
ax = plt.subplot2grid((1, 2), (0,0))
ax.imshow(mymap, alpha=1, zorder=0, extent=box)
plt.title("Local_Authority_(District)")
for i in range(0, 20):
    acc_LA = accidents.loc[accidents["Local_Authority_(District)"]==localAuth[i]]
    ax.scatter(
        acc_LA.Longitude, acc_LA.Latitude, 
        alpha=0.5, 
        s=1, 
        label = localAuth[i],
        c=colors[i]
    )
ax.legend(markerscale=10)

localAuth = df_byLA["Local_Authority_(Highway)"].values
ax2 = plt.subplot2grid((1, 2), (0,1))
ax2.imshow(mymap, alpha=1, zorder=0, extent=box)
plt.title("Local_Authority_(Highway)")
for i in range(0, 20):
    acc_LA = accidents.loc[accidents["Local_Authority_(Highway)"]==localAuth[i]]
    ax2.scatter(
        acc_LA.Longitude, acc_LA.Latitude, 
        alpha=0.5, 
        s=1,
        label = localAuth[i],
        c=colors[i]
    )
ax2.legend(markerscale=10)

plt.show()

Come varia il numero di incidenti per le diverse categorie stradali? (si rimanda al seguente link per capire meglio la classificazione delle strade nel Regno Unito: https://en.wikipedia.org/wiki/Road_hierarchy):

In [ ]:
f = plt.figure(figsize=(10,5))

df_byR = pd.DataFrame(accidents['1st_Road_Class'].value_counts().sort_index()).reset_index()
df_byR.columns = ["1st_Road_Class", "Numero di incidenti"]
df_byR = df_byR.sort_values(ascending=False, by=["Numero di incidenti"])[0:20]

plot = sns.barplot(x="1st_Road_Class", y="Numero di incidenti", palette="rocket", data=df_byR)
plot.get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plot.set_title("Numero di incidenti per categoria di strada")

percentage = np.array(df_byR[["Numero di incidenti"]]/accidents.shape[0] * 100)
patches = plot.patches

for i in range(len(patches)):
    x = patches[i].get_x() + patches[i].get_width()/2
    y = patches[i].get_height()+5000
    plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)

La categoria con maggiori incidenti è la categoria A (45.3% degli incidenti), per capire bene di che tipologie di strade sono, vengono plottati sulla cartina gli incidenti avvenuti in strade di categoria A:

In [ ]:
f = plt.figure(figsize=(8,12))

localAuth = df_byR["1st_Road_Class"].values

plt.imshow(mymap, alpha=1, zorder=0, extent=box)
plt.title("1st_Road_Class")
i=0
acc_R = accidents.loc[accidents["1st_Road_Class"]==localAuth[i]]
plt.scatter(
    acc_R.Longitude, acc_R.Latitude, 
    alpha=0.5, 
    s=1, 
    label = localAuth[i],
    c=colors[i]
)
plt.legend(markerscale=10)

plt.show()

Come si può vedere si ha un riscontro geografico delle strade di categoria A; mentre le Motorway che sarebbero le strade più importanti (con più di 3 corsie) hanno giustamente meno incidenti essendo di meno. Ecco gli incidenti avvenuti nelle Motorway:

In [ ]:
f = plt.figure(figsize=(8,12))

localAuth = df_byR["1st_Road_Class"].values

plt.imshow(mymap, alpha=1, zorder=0, extent=box)
plt.title("1st_Road_Class")
#for i in range(len(localAuth)):
i=4
acc_R = accidents.loc[accidents["1st_Road_Class"]==localAuth[i]]
plt.scatter(
    acc_R.Longitude, acc_R.Latitude, 
    alpha=0.5, 
    s=1, 
    label = localAuth[i],
    c=colors[i]
)
plt.legend(markerscale=10)

plt.show()

In che tipo di strada avvengono maggiormente gli incidenti? (Road_Type):

In [ ]:
f = plt.figure(figsize=(15,7))

df_byRT = pd.DataFrame(accidents['Road_Type'].value_counts().sort_index()).reset_index()
df_byRT.columns = ["Road_Type", "Numero di incidenti"]
df_byRT = df_byRT.sort_values(ascending=False, by=["Numero di incidenti"])[0:20]

plot = sns.barplot(x="Road_Type", y="Numero di incidenti", palette="rocket", data=df_byRT)
plot.get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plot.set_title("Numero di incidenti per tipo di strada")

percentage = np.array(df_byRT[["Numero di incidenti"]]/accidents.shape[0] * 100)
patches = plot.patches

for i in range(len(patches)):
    x = patches[i].get_x() + patches[i].get_width()/2
    y = patches[i].get_height()+10000
    plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)

In questo caso il numero maggiore di incidenti si ha per strade a singola carreggiata (74,6%) seguito da strade a doppia carreggiata (14,8%), seguito anche dalle rotatoie (6.7%) e cos' via...
Si visualizza sulla cartina:

In [ ]:
typeRoad = df_byRT["Road_Type"].values

righe=2
colonne=4
f = plt.figure(figsize=(8*colonne,12*righe))
k=0

for i in range(0, righe):
    for j in range(0, colonne):
        if k<len(typeRoad):
            acc_R = accidents.loc[accidents["Road_Type"]==typeRoad[k]]
            ax = plt.subplot2grid((righe, colonne), (i,j))
            ax.imshow(mymap, alpha=1, zorder=0, extent=box)
            ax.scatter(
                acc_R.Longitude, acc_R.Latitude, 
                alpha=0.5, 
                s=0.5,
                label = typeRoad[k],
                c= colors[k]
            )
            ax.legend(markerscale=10)
            k += 1
plt.show()

Caso interessante, gli incidenti avvenuti sulle rotatoie, geograficamente sono punti concentrati rispetto quelli avvenuti ad esempio nelle strade a singola carreggiata.

Come varia il numero di incidenti in base ai limiti di velocità? (Speed_Limit):

In [ ]:
f = plt.figure(figsize=(15,7))

df_bySL = pd.DataFrame(accidents['Speed_limit'].value_counts().sort_index()).reset_index()
df_bySL.columns = ["Speed_limit", "Numero di incidenti"]
df_bySL = df_bySL.sort_values(ascending=False, by=["Numero di incidenti"])[0:20]

plot = sns.barplot(x="Speed_limit", y="Numero di incidenti", palette="rocket", data=df_bySL, order=df_bySL["Speed_limit"])
plot.get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plot.set_title("Numero di incidenti per limite di velocità")

percentage = np.array(df_bySL[["Numero di incidenti"]]/accidents.shape[0] * 100)
patches = plot.patches

for i in range(len(patches)):
    x = patches[i].get_x() + patches[i].get_width()/2
    y = patches[i].get_height()+10000
    plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)

Il 63.8% degli incidenti è avvenuto in strade con limite di velocità 30 mph, ossia circa 48 km/h; che non è una velocità elevata... Ma questo è dovuto proprio al fatto che tali limiti si trovano più in strade vicino le città e quindi con un numero molto più elevato di veicoli in circolazione.

Per rendersi conto meglio che strade con limiti di velocità più bassi sono nelle città o nei pressi delle città, rispetto le strade con alti limiti di velocità (es. 70 mph) che si trovano più distanti dalle metropoli (autostrade, ecc.); si plottano le due cartine con i due limiti di velocità (limite minimo e limite massimo):

In [ ]:
righe=1
colonne=2
f = plt.figure(figsize=(8*colonne,12*righe))

acc_S = accidents.loc[accidents["Speed_limit"]==30]
ax = plt.subplot2grid((righe, colonne), (0,0))
ax.imshow(mymap, alpha=1, zorder=0, extent=box)
ax.scatter(
    acc_S.Longitude, acc_S.Latitude, 
    alpha=0.5, 
    s=1,
    label = "30 mph (48.28 km/h)",
    c= colors[0]
)
ax.legend(markerscale=10)

acc_S = accidents.loc[accidents["Speed_limit"]==70]
ax = plt.subplot2grid((righe, colonne), (0,1))
ax.imshow(mymap, alpha=1, zorder=0, extent=box)
ax.scatter(
    acc_S.Longitude, acc_S.Latitude, 
    alpha=0.5, 
    s=1,
    label = "70 mph (112.65 km/h)",
    c= colors[1]
)
ax.legend(markerscale=10)

plt.show()

Come volevasi dimostrare nella mappa a destra si notano molto di più le strade (autostrade), rispetto quella di sinistra. Ad esempio il centro di Londra è presente nella mappa a sinistra e non in quella in destra.

Come varia il numero di morti (Number_of_Casualties) al variare dei limiti di velocità? Ci si aspetta che per strade con limiti maggiori di velocità ci siano più morti. Si controllerà il numero di morti relativo, ovvero il rapporto della somma del numero di morti per il numero di incidenti raggruppati per limiti di velocità:

In [ ]:
f = plt.figure(figsize=(15,7))

df_bySLM = accidents.groupby("Speed_limit", as_index=False).agg({"Number_of_Casualties": np.sum, "Accident_Index": np.size})
df_bySLM.columns = ["Speed_limit", "Numero di morti", "Numero di incidenti"]
df_bySLM['Numero di morti relativo'] = df_bySLM.apply(lambda row: row["Numero di morti"] / row["Numero di incidenti"], axis=1)
df_bySLM.sort_values(by=["Numero di morti relativo"], inplace=True, ascending=False)

plot = sns.barplot(x="Speed_limit", y="Numero di morti relativo", palette="rocket", data=df_bySLM, order=df_bySLM["Speed_limit"])
plot.get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plot.set_title("Numero di incidenti per limite di velocità")

percentage = np.array(df_bySLM[["Numero di morti relativo"]]/df_bySLM["Numero di morti relativo"].sum() * 100)
patches = plot.patches

for i in range(len(patches)):
    x = patches[i].get_x() + patches[i].get_width()/2
    y = patches[i].get_height()+0.005
    plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)

Come ci si aspettava, il numero di morti relativo si ha per strade con limiti di velocità più alti; proprio perchè all'aumentare delle velocità c'è più probabilità di morire in caso di incidente.

Dove si trovano i 10 incidenti con il maggior numero di morti e con il maggior numero di veicoli coinvolti (Number_of_Vehicles):

In [ ]:
righe=1
colonne=2
f = plt.figure(figsize=(8*colonne,12*righe))

acc_casualties = accidents.sort_values(ascending=False, by=["Number_of_Casualties"])[0:10]
ax = plt.subplot2grid((righe, colonne), (0,0))
ax.imshow(mymap, alpha=1, zorder=0, extent=box)
ax.scatter(
    acc_casualties.Longitude, acc_casualties.Latitude, 
    alpha=1, 
    s=5*acc_casualties["Number_of_Casualties"],
    label = "Top 10 Incidenti con maggior numero di morti",
    c= colors[0]
)
ax.plot(acc_casualties.Longitude.values[0], acc_casualties.Latitude.values[0], marker="o", markersize=20, markeredgecolor="red", markerfacecolor="green", label="MAX")
ax.legend(markerscale=1)
for i, txt in enumerate(acc_casualties.Number_of_Casualties.values):
    ax.annotate(txt, (acc_casualties.Longitude.values[i]+0.2, acc_casualties.Latitude.values[i]+0.05), fontsize=14)

acc_vehicles = accidents.sort_values(ascending=False, by=["Number_of_Vehicles"])[0:10]
ax = plt.subplot2grid((righe, colonne), (0,1))
ax.imshow(mymap, alpha=1, zorder=0, extent=box)
ax.scatter(
    acc_vehicles.Longitude, acc_vehicles.Latitude, 
    alpha=1, 
    s=5*acc_vehicles["Number_of_Vehicles"],
    label = "Top 10 Incidenti con maggior numero di veicoli coinvolti",
    c= colors[2]
)
ax.plot(acc_vehicles.Longitude.values[0], acc_vehicles.Latitude.values[0], marker="o", markersize=20, markeredgecolor="red", markerfacecolor="green", label="MAX")
ax.legend(markerscale=1)
for i, txt in enumerate(acc_vehicles.Number_of_Vehicles.values):
    ax.annotate(txt, (acc_vehicles.Longitude.values[i]+0.2, acc_vehicles.Latitude.values[i]+0.05), fontsize=14)

plt.show()
print("Massimo numero di morti in un incidente:", acc_casualties.Number_of_Casualties.values[0])
print("Massimo numero di veicoli coinvolti in un incidente:", acc_vehicles.Number_of_Vehicles.values[0])
Massimo numero di morti in un incidente: 93
Massimo numero di veicoli coinvolti in un incidente: 67

Come è variato invece, il numero di morti negli anni:

In [ ]:
f = plt.figure(figsize=(20,7))

df_byYearSev = accidents.groupby("Year", as_index=False).agg({"Number_of_Casualties": np.sum, "Accident_Index": np.size})
df_byYearSev.columns = ["Anno", "Numero di morti", "Numero di incidenti"]
df_byYearSev.sort_values(by=["Numero di incidenti"], inplace=True, ascending=False)

plot = sns.barplot(data=df_byYearSev.melt(id_vars='Anno', value_vars=['Numero di morti', 'Numero di incidenti']), x='Anno', y='value', hue='variable', palette="rocket")
plot.get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plot.set_title("Numero di incidenti/morti per ogni anno dal 2005 al 2016")

percentage = np.array(df_byYearSev[["Numero di morti"]]/accidents["Number_of_Casualties"].sum() * 100)
percentage = np.concatenate((percentage, np.array(df_byYearSev[["Numero di incidenti"]]/accidents.shape[0] * 100)))
patches = plot.patches

for i in range(len(patches)):
    x = patches[i].get_x() + patches[i].get_width()/2
    y = patches[i].get_height()+1000
    plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)
In [ ]:
f = plt.figure(figsize=(20,7))

df_byYearSev['Numero di morti relativo'] = df_byYearSev.apply(lambda row: row["Numero di morti"] / row["Numero di incidenti"], axis=1)
df_byYearSev.sort_values(by=["Numero di morti relativo"], inplace=True, ascending=False)

plot = sns.barplot(x="Anno", y="Numero di morti relativo", palette="rocket", data=df_byYearSev)
plot.get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plot.set_title("Numero di morti relativo per ogni anno dal 2005 al 2016")

percentage = np.array(df_byYearSev[["Numero di morti relativo"]]/df_byYearSev["Numero di morti relativo"].sum() * 100)
patches = plot.patches

for i in range(len(patches)):
    x = patches[i].get_x() + patches[i].get_width()/2
    y = patches[i].get_height()+0.005
    plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)

Il numero di morti ha avuto un andamento quasi costante negli anni.

Come variano il numero di morti e di incidenti nei mesi?

In [ ]:
accidents['Month'] = pd.DatetimeIndex(accidents['Date']).month
accidents['Month'] = accidents['Month'].apply(lambda x: calendar.month_abbr[x])

f = plt.figure(figsize=(20,7))

df_byMonth = accidents.groupby("Month", as_index=False).agg({"Number_of_Casualties": np.sum, "Accident_Index": np.size})
df_byMonth.columns = ["Mese", "Numero di morti", "Numero di incidenti"]
df_byMonth.sort_values(by=["Numero di incidenti"], inplace=True, ascending=False)

plot = sns.barplot(data=df_byMonth.melt(id_vars='Mese', value_vars=['Numero di morti', 'Numero di incidenti']), x='Mese', y='value', hue='variable', palette="rocket")
plot.get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plot.set_title("Numero di incidenti/morti per mesi dell'anno")

percentage = np.array(df_byMonth[["Numero di morti"]]/accidents["Number_of_Casualties"].sum() * 100)
percentage = np.concatenate((percentage, np.array(df_byMonth[["Numero di incidenti"]]/accidents.shape[0] * 100)))
patches = plot.patches

for i in range(len(patches)):
    x = patches[i].get_x() + patches[i].get_width()/2
    y = patches[i].get_height()+1000
    plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)

accidents.drop(labels='Month', axis=1, inplace=True)

Il mese di novembre è stato quello con maggiori incidenti, seguito da ottobre, luglio ecc.

Come variano il numero di morti e di incidenti nei giorni della settimana?

In [ ]:
f = plt.figure(figsize=(20,7))

df_byWeek = accidents.groupby("Day_of_Week", as_index=False).agg({"Number_of_Casualties": np.sum, "Accident_Index": np.size})
df_byWeek.columns = ["Giorno", "Numero di morti", "Numero di incidenti"]
df_byWeek.sort_values(by=["Numero di incidenti"], inplace=True, ascending=False)

plot = sns.barplot(data=df_byWeek.melt(id_vars='Giorno', value_vars=['Numero di morti', 'Numero di incidenti']), x='Giorno', y='value', hue='variable', palette="rocket")
plot.get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plot.set_title("Numero di incidenti/morti per giorni della settimana")

percentage = np.array(df_byWeek[["Numero di morti"]]/accidents["Number_of_Casualties"].sum() * 100)
percentage = np.concatenate((percentage, np.array(df_byWeek[["Numero di incidenti"]]/accidents.shape[0] * 100)))
patches = plot.patches

for i in range(len(patches)):
    x = patches[i].get_x() + patches[i].get_width()/2
    y = patches[i].get_height()+3000
    plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)

Il giorno della settimana con più incidenti (e anche morti) è il venerdì; molto probabile perchè il venerdì ci sono più spostamenti dovuti per la fine settimana lavorativa.

Come variano il numero di morti e di incidenti nelle ore della giornata?

In [ ]:
accidents['Time_Interval'] = pd.to_datetime(accidents['Time'])
accidents['Time_Interval'] = accidents['Time_Interval'].dt.strftime('%H')
In [ ]:
f = plt.figure(figsize=(20,7))

df_byHour = accidents.groupby("Time_Interval", as_index=False).agg({"Number_of_Casualties": np.sum, "Accident_Index": np.size})
df_byHour.columns = ["Ora", "Numero di morti", "Numero di incidenti"]
df_byHour.sort_values(by=["Numero di incidenti"], inplace=True, ascending=False)

plot = sns.barplot(data=df_byHour.melt(id_vars='Ora', value_vars=['Numero di morti', 'Numero di incidenti']), x='Ora', y='value', hue='variable', palette="rocket")
plot.get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
plot.set_title("Numero di incidenti/morti per ora del giorno")

percentage = np.array(df_byHour[["Numero di morti"]]/accidents["Number_of_Casualties"].sum() * 100)
percentage = np.concatenate((percentage, np.array(df_byHour[["Numero di incidenti"]]/accidents.shape[0] * 100)))
patches = plot.patches

for i in range(len(patches)):
    x = patches[i].get_x() + patches[i].get_width()/2
    y = patches[i].get_height()+1000
    plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)

accidents.drop(labels='Time_Interval', axis=1, inplace=True)

Il maggior numero di incidenti/morti si hanno nel pomeriggio; ancora una volta una possibile motivazione può essere la maggiore affluenza dei veicoli in quelle ore (molti escono da lavoro). Mentre nelle ore di notte si hanno meno incidenti, anche se in proporzione si ha un maggior numero di morti, questo può essere giustificato dal fatto che la notte è più probabile trovare persone che guidano in stato di ebbrezza.

Ulteriori analisi verranno effettuate nel Capitolo 4: Pre-Processing, sezione 4.2 Esplorazione delle features.

4. Pre-Processing¶

In questa fase, verrà prima di tutto ripulito il dataset originale, rimuovendo ad esempio tutti i valori null e outliers; effettuando anche un ribilanciamento del dataset. In un secondo momento invece si effetturà una feature selection per andare ad individuare un sottoinsieme delle colonne del dataset, che poi andranno a formare il dataset finale; ed infine nell'ultima parte verranno effettuate tutte quelle trasformazioni sui dati per rendere il dataset "appetibile" agli algoritmi di data mining.

4.1. Data Cleaning¶

Si ricorda che il dataset che si ha come riferimento è train_set, ovvero quello proveniente dal captolo 2 Preparazione dei dati; in cui è stato effettuato lo split del train e test set a partire dal dataset risultante dalla inner join di accidents e vehicles.

In [ ]:
print('Training set:', train_set.shape)
train_set.head()
Training set: (2037823, 56)
Out[ ]:
Accident_Index Age_Band_of_Driver Age_of_Vehicle Driver_Home_Area_Type Driver_IMD_Decile Engine_Capacity_.CC. Hit_Object_in_Carriageway Hit_Object_off_Carriageway Journey_Purpose_of_Driver Junction_Location make model Propulsion_Code Sex_of_Driver Skidding_and_Overturning Towing_and_Articulation Vehicle_Leaving_Carriageway Vehicle_Location.Restricted_Lane Vehicle_Manoeuvre Vehicle_Reference Vehicle_Type Was_Vehicle_Left_Hand_Drive X1st_Point_of_Impact Year 1st_Road_Class 1st_Road_Number 2nd_Road_Class 2nd_Road_Number Accident_Severity Carriageway_Hazards Date Day_of_Week Did_Police_Officer_Attend_Scene_of_Accident Junction_Control Junction_Detail Latitude Light_Conditions Local_Authority_(District) Local_Authority_(Highway) Location_Easting_OSGR Location_Northing_OSGR Longitude LSOA_of_Accident_Location Number_of_Casualties Number_of_Vehicles Pedestrian_Crossing-Human_Control Pedestrian_Crossing-Physical_Facilities Police_Force Road_Surface_Conditions Road_Type Special_Conditions_at_Site Speed_limit Time Urban_or_Rural_Area Weather_Conditions InScotland
1412192 2014074244006 Data missing or out of range 11.0 Data missing or out of range NaN 1995.0 None None Not known Not at or within 20 metres of junction BMW 320 D SE TOURING Heavy oil Male None No tow/articulation Did not leave carriageway 0.0 Parked 2 Car No Nearside 2014 Unclassified 0.0 NaN 0.0 Slight None 2014-08-18 Monday 1.0 Data missing or out of range Not at junction or within 20 metres 53.388626 Daylight Warrington Warrington 369659.0 388075.0 -2.457681 E01012517 2 2 0.0 0.0 Cheshire Dry Single carriageway None 30.0 15:49 Urban Fine no high winds No
2057144 201697UA05912 26 - 35 7.0 Urban area NaN 1108.0 None None Not known Approaching junction or waiting/parked at junc... FIAT PANDA ACTIVE ECO Petrol Female None No tow/articulation Did not leave carriageway 0.0 Going ahead other 2 Car No Did not impact 2016 B 780.0 Unclassified 0.0 Slight None 2016-12-26 Monday 1.0 Give way or uncontrolled T or staggered junction 55.633989 Darkness - lights lit North Ayrshire North Ayrshire 224716.0 641342.0 -4.786159 NaN 1 2 0.0 4.0 Strathclyde Wet or damp Single carriageway None 30.0 21:31 Urban Fine no high winds Yes
1935690 201631D088016 21 - 25 8.0 Data missing or out of range NaN 124.0 None None Not known Not at or within 20 metres of junction KYMCO KR SPORT 125 Petrol Male None No tow/articulation Did not leave carriageway 0.0 Going ahead other 1 Motorcycle 125cc and under No Front 2016 Unclassified 0.0 NaN 0.0 Slight None 2016-05-08 Sunday 1.0 Data missing or out of range Not at junction or within 20 metres 52.952462 Daylight Rushcliffe Nottinghamshire 473991.0 340022.0 -0.900129 E01028371 1 2 0.0 0.0 Nottinghamshire Dry Single carriageway None 30.0 12:49 Rural Fine no high winds No
725183 2010620201503 21 - 25 12.0 Urban area NaN 1124.0 None None Other/Not known (2005-10) Approaching junction or waiting/parked at junc... PEUGEOT 106 XL INDEPENDENCE Petrol Male None No tow/articulation Did not leave carriageway 0.0 Changing lane to left 1 Car No Front 2010 A 4118.0 Unclassified 0.0 Slight None 2010-06-07 Monday 1.0 Give way or uncontrolled Slip road 51.629302 Daylight Swansea Swansea 265610.0 194070.0 -3.943080 W01000744 1 2 0.0 0.0 South Wales Wet or damp Dual carriageway None 30.0 16:40 Urban Raining + high winds No
2008176 2016471601947 46 - 55 2.0 Urban area 8.0 2199.0 None None Not known Approaching junction or waiting/parked at junc... KIA SORENTO KX-2 CRDI 4X4 Heavy oil Male None No tow/articulation Did not leave carriageway 0.0 Waiting to go - held up 2 Car No Nearside 2016 A 27.0 A 284.0 Slight None 2016-04-03 Sunday 1.0 Auto traffic signal Crossroads 50.842999 Daylight Arun West Sussex 502934.0 105854.0 -0.539367 E01031392 2 3 0.0 0.0 Sussex Dry Dual carriageway None 40.0 08:03 Rural Fine no high winds No

Si effettua una copia dell'originale set di addestramento, che servirà dopo per effettuare la fit su una pipeline delle trasformazioni:

In [ ]:
train_set_iniziale = train_set.copy()

Si rimuovono i duplicati (se presenti):

In [ ]:
print("Prima -->", train_set.shape)
train_set.drop_duplicates(keep='first', inplace=True)
print("Dopo -->", train_set.shape)
Prima --> (2037823, 56)
Dopo --> (2037823, 56)

Non erano presenti duplicati.

4.1.1. Eliminazine valori nulli¶

Adesso si eliminano features che contengono troppi valori null, visti nel grafico della sezione precedente, e riportati nel grafico sottostante per il training set:

In [ ]:
plt.figure(figsize=(15,5))
s = pd.Series(train_set.isnull().sum()/train_set.shape[0]).sort_values(ascending=False)
sns.barplot(x = s.index, y = s.values)
plt.xticks(rotation=90)
plt.title("Valori nulli nelle features in percentuale", size=20)
Out[ ]:
Text(0.5, 1.0, 'Valori nulli nelle features in percentuale')

Però non sono solo questi i valori null; infatti sono presenti altri valori null che però non vengono catturati, siccome in realtà sono salvati come un valore categorico. Infatti il valore "Data missing or out of range" lo si può sostituire con il valore NaN, così da non trascurare tali valori null.
Si crea una classe per un transformer che effettua la replace dei valori "Data missing or out of range" in np.NaN, che verrà inserito più avanti in una pipeline:

In [ ]:
class ReplaceValuesTransformer(BaseEstimator, TransformerMixin):
    def __init__(self,valueFrom, valueTo):
        self.valueFrom=valueFrom
        self.valueTo=valueTo

    def transform(self,X,y=None):
        return X.replace(self.valueFrom, self.valueTo)

    def fit(self, X, y=None):
        return self

#pipeline = Pipeline([
#    ("replaceValuesToNan", replaceValuesTransformer("Data missing or out of range", np.NaN))
#])
In [ ]:
replaceValuesToNan = ReplaceValuesTransformer('Data missing or out of range', np.NaN)
train_set = replaceValuesToNan.fit_transform(train_set)

Viene riportato il grafico aggiornato dei valori null:

In [ ]:
plt.figure(figsize=(15,5))
s = pd.Series(train_set.isnull().sum()/train_set.shape[0]).sort_values(ascending=False)
sns.barplot(x = s.index, y = s.values)
plt.xticks(rotation=90)
plt.title("Valori nulli nelle features in percentuale", size=20)
Out[ ]:
Text(0.5, 1.0, 'Valori nulli nelle features in percentuale')

Si nota come sono comparsi attributi con molti valori null che prima erano sfuggiti, come l'attributo Junction_Control.

In particolare si effettua la drop delle seguenti colonne:

  • Driver_IMD_Decile, model, 2nd_Road_Class, 2nd_Road_Number, LSOA_of_Accident_Location e Junction_Control.
In [ ]:
print("Prima -->", train_set.shape)
train_set.drop(labels='Driver_IMD_Decile', axis=1, inplace=True)
train_set.drop(labels='model', axis=1, inplace=True)
train_set.drop(labels='2nd_Road_Class', axis=1, inplace=True)
train_set.drop(labels='2nd_Road_Number', axis=1, inplace=True)
train_set.drop(labels='LSOA_of_Accident_Location', axis=1, inplace=True)
train_set.drop(labels='Junction_Control', axis=1, inplace=True)
print("Dopo -->", train_set.shape)
Prima --> (2037823, 56)
Dopo --> (2037823, 50)

Si eliminano anche altre features che non sono di interesse per il task; ad esempio Date, oppure Location_Easting_OSGR e Location_Northing_OSGR perchè sono un altro modo per localizzare l'incidente (bastano Latitude e Longitude):

In [ ]:
print("Prima -->", train_set.shape)
train_set.drop(labels='Date', axis=1, inplace=True)
train_set.drop(labels='Location_Easting_OSGR', axis=1, inplace=True)
train_set.drop(labels='Location_Northing_OSGR', axis=1, inplace=True)
print("Dopo -->", train_set.shape)
Prima --> (2037823, 50)
Dopo --> (2037823, 47)

Vengono rimosse altre features che non sono utili ai fini del task come l'indice dell'incidente e del veicolo:

In [ ]:
print("Prima -->", train_set.shape)
train_set.drop(labels='Accident_Index', axis=1, inplace=True)
train_set.drop(labels='Vehicle_Reference', axis=1, inplace=True)
train_set.drop(labels='Police_Force', axis=1, inplace=True)
train_set.drop(labels='1st_Road_Number', axis=1, inplace=True)
print("Dopo -->", train_set.shape)
Prima --> (2037823, 47)
Dopo --> (2037823, 43)

Per quanto riguarda Age_of_Vehicle, EngineCapacity.CC., make e Propulsion_Code, non le si eliminano come features perchè potrebbero essere interessanti.
Si crea una classe per un transformer che effettua la drop di colonne, che verrà inserito più avanti in una pipeline:

In [ ]:
class ColumnDropperTransformer(BaseEstimator, TransformerMixin):
    def __init__(self,columns):
        self.columns=columns

    def transform(self,X,y=None):
        return X.drop(self.columns,axis=1)

    def fit(self, X, y=None):
        return self

#pipeline = Pipeline([
#    ("columnDropper", ColumnDropperTransformer(['col_2','col_3']))
#])

Si eliminano tutte le righe che contengono almeno un valore null. Questa scelta è giustificata dall'enorme dataset che abbiamo a disposizione.
Si crea una classe per un transformer che effettua la drop delle righe (passando come parametro l'indice delle righe da eliminare, se non definito verranno eliminate tutte le righe che contengono almeno un valore nullo), che verrà inserito più avanti in una pipeline:

In [ ]:
class RowsDropperTransformer(BaseEstimator, TransformerMixin):
    def __init__(self, indexNames=None):
        self.indexNames = indexNames

    def transform(self,X,y=None):
        if self.indexNames is None:
            return X.dropna()
        if self.indexNames == 'Age_Band_of_Driver':
            index = X[ (X[self.indexNames] == '0 - 5') | (X[self.indexNames] == '6 - 10') ].index
        else:
            index = X[ (X[self.indexNames] == 10.0) | (X[self.indexNames] == 15.0) ].index
        return X.drop(index)

    def fit(self, X, y=None):
        return self

#pipeline = Pipeline([
#    ("rowsDropper", RowsDropperTransformer())
#])
In [ ]:
print("Prima -->", train_set.shape)
rowsNanDropper = RowsDropperTransformer()
train_set = rowsNanDropper.fit_transform(train_set)
print("Dopo -->", train_set.shape)
Prima --> (2037823, 43)
Dopo --> (1418652, 43)

A questo punto non restano campi con valori nulli:

In [ ]:
for i in train_set.isnull().sum():
    if i!=0:
        print("Sono presenti valori null!")
        train_set.isnull().sum()
        break
print("Non sono presenti valori null!")
Non sono presenti valori null!

4.1.2. Eliminazione outliers¶

In questa fase, invece si ricercano eventuali outliers nelle features numeriche, attraverso l'ausilio dei boxplot.
In particolare, attualmente le colonne numeriche rimanenti sono le seguenti:

In [ ]:
train_set.select_dtypes(["float", "int"]).columns
Out[ ]:
Index(['Age_of_Vehicle', 'Engine_Capacity_.CC.',
       'Vehicle_Location.Restricted_Lane', 'Year',
       'Did_Police_Officer_Attend_Scene_of_Accident', 'Latitude', 'Longitude',
       'Number_of_Casualties', 'Number_of_Vehicles',
       'Pedestrian_Crossing-Human_Control',
       'Pedestrian_Crossing-Physical_Facilities', 'Speed_limit'],
      dtype='object')

Di queste, le più importanti sono:

  • Age_of_Vehicle
  • EngineCapacity.CC.
  • Latitude
  • Longitude
  • Number_of_Casualties
  • Number_of_Vehicles
  • Speed_limit

Le restanti saranno eliminate più avanti. Per qunato riguarda Latitude e Longitude, esse potrebbero presentare degli outliers corrspondenti agli incidenti avvenuti nell'isola Mainland, ovvero l'isola più a nord-est della Gran Bretagna, e quindi giustamente le coordinate degli incidenti si differenziano di molto dal resto delle coordinate. Però comunque si ritiene che tali valori non sono outliers perchè possono dare lo stesso un contributo nella fase di learning.

Quindi, le colonne che possono contenere degli outliers sono:

In [ ]:
col_outliers = ['Age_of_Vehicle', 'Engine_Capacity_.CC.', 'Number_of_Casualties', 'Number_of_Vehicles', 'Speed_limit']

Si valuteranno i boxplot di ogniuna, suddivisi per gravità di incidente (Accident_Severity).

In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Age_of_Vehicle', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Age_of_Vehicle'>
In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Engine_Capacity_.CC.', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Engine_Capacity_.CC.'>
In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Number_of_Casualties', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Number_of_Casualties'>
In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Number_of_Vehicles', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Number_of_Vehicles'>
In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Speed_limit', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Speed_limit'>

Per il momento si considerano ouliers, tutti quei punti che superano 1.5 IQR.
Sotto tale ipotesi, i risultati ottenuti mostrano che a parte la colonna
Speed_limit, le altre contengono molti ouliers.
A parte la colonna
Age_of_Vehicle che contiene molti ouliers ma abbastanza comuni tra loro, le altre 3 colonne presentano dei veri e propri punti isolati. Basti vedere ad esempio la colonna Number_of_Vehicles* che contiene un incidente con circa 70 veicoli coinvolti, questo sicuramente è un outliers, dato che è un evento rarissimo (infatti solo un incidente del genere).

Per curiosità, su tale incidente si può andare a verificare cosa sia successo realmente quel giorno. Prendendo le coordinate dell'incidente e l'anno:

In [ ]:
df = train_set[train_set['Number_of_Vehicles'] > 60].head(1)
lat, long, year = df.iloc[0]['Latitude'], df.iloc[0]['Longitude'], df.iloc[0]['Year']
lat, long, year
Out[ ]:
(51.39166, 0.749417, 2013)

In particolare le coordinate sopra menzionate corrispondono al ponte Sheppy Crossing a Sittingbourne in Inghilterra. Infatti nel 2013 su quel ponte c'è stato un enorme incidente di veicoli (pile-up), molto probabilmente dovuto anche all'alto livello di nebbia presente. Per maggiori informazioni ecco il link di Wikipedia che parla dello Sheppy Crossing crash: https://en.wikipedia.org/wiki/Sheppey_Crossing_crash

A questo punto si è deciso di rimuovere tutte le tuple che corrispondo a degli outliers, ovvero quei punti che superano di 1.5 * IQR. Si crea un transformer ad hoc, che verrà usato più avanti all'interno della pipeline:

In [ ]:
class RowsDropperOutliersTransformer(BaseEstimator, TransformerMixin):
    def __init__(self, indexNames=None, val_outliers=1.5):
        self.indexNames = indexNames
        self.val_outliers = val_outliers

    def transform(self,X,y=None, remove_outliers=True):
        if remove_outliers:
            self.index_to_drop = self.__outlier_detection(X, self.indexNames)
            X_clean = X.drop(self.index_to_drop)
        else:
            X_clean = X.copy()
        return X_clean

    def fit(self, X, y=None):
        return self

    def __outlier_detection(self, df, columns):
      rows = []
      to_drop_train = []
      df_slight = df[df['Accident_Severity'] == 'Slight']
      df_serious = df[df['Accident_Severity'] == 'Serious']
      df_fatal = df[df['Accident_Severity'] == 'Fatal']
      for col in columns:
          for dfn in (df_slight, df_serious, df_fatal):
            Q1 = np.nanpercentile(dfn[col], 25)
            Q3 = np.nanpercentile(dfn[col], 75)
            IQR = Q3 - Q1
            outlier_point = self.val_outliers * IQR
            rows.extend(dfn[(dfn[col] < Q1 - outlier_point)|(dfn[col] > Q3 + outlier_point)].index)
      to_drop_train = np.unique(rows)
      return to_drop_train
In [ ]:
print("Prima -->", train_set.shape)
outlierTransformer = RowsDropperOutliersTransformer(col_outliers)
train_set = outlierTransformer.transform(train_set, remove_outliers=True)
print("Dopo -->", train_set.shape)
Prima --> (1418652, 43)
Dopo --> (847994, 43)

Per verificare che gli outliers non sono più presenti basta andare a riplottare i boxplot. Essi saranno ripresentati nella sezione 4.2.2. Esplorazione attributi numerici in 4.2. Esplorazione delle features.

4.1.3. Ribilanciamento delle classi¶

Un problema importante adesso da risolvere prima di proseguire è lo sbilanciamento della classe target; ci si trova in un problema di imbalanced learning:

In [ ]:
def barPlotAccidentSeverity(label_pos_y=10000):
    f = plt.figure(figsize=(15,7))
    df_byAccSev = train_set.groupby("Accident_Severity", as_index=False).size()
    df_byAccSev.columns = ["Accident_Severity", "Numero di incidenti"]
    df_byAccSev.sort_values(by=["Numero di incidenti"], inplace=True, ascending=False)


    plot = sns.barplot(x="Accident_Severity", y="Numero di incidenti", palette="rocket", data=df_byAccSev)
    plot.get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))
    plot.set_title("Numero di incidenti per gravità dell'incidente")

    percentage = np.array(df_byAccSev[["Numero di incidenti"]]/train_set.shape[0] * 100)
    patches = plot.patches

    for i in range(len(patches)):
        x = patches[i].get_x() + patches[i].get_width()/2
        y = patches[i].get_height()+label_pos_y
        plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)
In [ ]:
barPlotAccidentSeverity()

Come si vede l'80.4% delle istanze è di tipo Slight, il 17.7% di tipo Serious e peggio ancora davvero una piccolissima percentuale di tipo Fatal...
L'idea innanzitutto è quella di trasformare il problema da un problema di classificazione multiclasse in un problema di classificazione binaria; verranno raggruppati in un'unica classe Serious e Fatal. Così facendo si andrà a diminuire di poco (1,9%) lo sbilanciamento.
Dopo questa fase quindi il task sarà la classificazione di un problema binario, dove Accident_Severity sarà:

  • 0: l'incidente non è stato grave (Slight).
  • 1: l'incidente è stato grave (Serious or Fatal).

Si definisce una funzione per effettuare tale trasformazione:

In [ ]:
def transformAccidentSverity(df):
    df['Accident_Severity'] = df['Accident_Severity'].replace(['Serious', 'Fatal'], 'Serious or Fatal')
    df = pd.get_dummies(df, columns=['Accident_Severity'])
    df = df.drop('Accident_Severity_Slight', axis=1)
    df['Accident_Severity_Serious or Fatal'].value_counts(normalize=True)
    df = df.rename(columns={'Accident_Severity_Serious or Fatal': 'Accident_Severity'})
    return df.drop('Accident_Severity', axis=1), df['Accident_Severity']
In [ ]:
train_set, y = transformAccidentSverity(train_set)
train_set['Accident_Severity'] = y

Più avanti tale funzione verrà usata all'interno di un FunctionTransformer.

In [ ]:
barPlotAccidentSeverity()

Nonostante il lieve miglioramento dello sbilanciamento, ancora c'è un forte sbilanciamento verso gli incidenti lievi (0). In questo caso anche un classificatore stupido che predice sempre il valore 0, otterrà un'accuratezza elevata dell'80,4%!
Per risolvere tale problema, grazie al fatto che il dataset è costituito da molte istanze, si effettua un massiccio undersampling sulla classe maggioritaria (0). Con l'ausilio della libreria imbalanced-learn si procede con un undersampling random, in modo da avere un minor numero di tuple ma un bilanciamento perfetto delle classi. (Per maggiori informazioni sulla libreria imbalanced_learn si rimanda alla documentazione: https://imbalanced-learn.org/stable/index.html).

In [ ]:
rus = RandomUnderSampler(random_state=0)
train_set, train_set['Accident_Severity']  = rus.fit_resample(train_set.loc[:, train_set.columns != 'Accident_Severity'], train_set['Accident_Severity'])
In [ ]:
barPlotAccidentSeverity(label_pos_y=1000)

Definiamo una funzione che effettua l'undersampling in modo tale da poterla richiamare più avanti all'interno di un FunctionTransformer:

In [ ]:
def underSampling(df):
    df, y = df
    rus = RandomUnderSampler(random_state=0)
    df, y  = rus.fit_resample(df.loc[:, df.columns != 'Accident_Severity'], y)
    return df, y

Al costo di perdere istanze del dataset, grazie all'undersampling le classi sono perfettamente bilanciate, come si vede nel diagramma. Ecco di seguito il dataset finale:

In [ ]:
print(train_set.shape)
train_set.head()
(332578, 43)
Out[ ]:
Age_Band_of_Driver Age_of_Vehicle Driver_Home_Area_Type Engine_Capacity_.CC. Hit_Object_in_Carriageway Hit_Object_off_Carriageway Journey_Purpose_of_Driver Junction_Location make Propulsion_Code Sex_of_Driver Skidding_and_Overturning Towing_and_Articulation Vehicle_Leaving_Carriageway Vehicle_Location.Restricted_Lane Vehicle_Manoeuvre Vehicle_Type Was_Vehicle_Left_Hand_Drive X1st_Point_of_Impact Year 1st_Road_Class Carriageway_Hazards Day_of_Week Did_Police_Officer_Attend_Scene_of_Accident Junction_Detail Latitude Light_Conditions Local_Authority_(District) Local_Authority_(Highway) Longitude Number_of_Casualties Number_of_Vehicles Pedestrian_Crossing-Human_Control Pedestrian_Crossing-Physical_Facilities Road_Surface_Conditions Road_Type Special_Conditions_at_Site Speed_limit Time Urban_or_Rural_Area Weather_Conditions InScotland Accident_Severity
0 46 - 55 9.0 Rural 1598.0 None None Journey as part of work Entering roundabout MINI Petrol Female None No tow/articulation Did not leave carriageway 0.0 Turning right Car No Did not impact 2013 A None Tuesday 1.0 Roundabout 53.047616 Daylight Newcastle-under-Lyme Staffordshire -2.244131 1 2 0.0 0.0 Wet or damp Roundabout None 40.0 07:40 Urban Raining no high winds No 0
1 46 - 55 6.0 Rural 1998.0 None None Not known Approaching junction or waiting/parked at junc... RENAULT Petrol Female None No tow/articulation Did not leave carriageway 0.0 Turning right Car No Nearside 2011 C None Wednesday 2.0 Private drive or entrance 55.949146 Daylight Edinburgh, City of Edinburgh, City of -3.184619 1 2 0.0 0.0 Dry Single carriageway None 30.0 16:10 Urban Fine no high winds Yes 0
2 56 - 65 3.0 Urban area 2231.0 None None Commuting to/from work Mid Junction - on roundabout or on main road TOYOTA Heavy oil Male None No tow/articulation Did not leave carriageway 0.0 Turning right Car No Front 2011 A None Wednesday 1.0 Crossroads 53.762771 Daylight Preston Lancashire -2.740800 3 2 0.0 0.0 Dry Single carriageway None 30.0 08:25 Urban Fine no high winds No 0
3 46 - 55 7.0 Urban area 2188.0 None None Commuting to/from work Entering main road RENAULT Heavy oil Male None No tow/articulation Did not leave carriageway 0.0 Turning left Car No Front 2012 A None Wednesday 1.0 Roundabout 51.616884 Daylight Barnet Barnet -0.244740 2 2 0.0 8.0 Wet or damp Single carriageway None 30.0 06:55 Urban Unknown No 0
4 21 - 25 6.0 Urban area 1968.0 None None Not known Entering roundabout VOLKSWAGEN Heavy oil Male None No tow/articulation Did not leave carriageway 0.0 Going ahead other Car No Offside 2012 A None Wednesday 1.0 Roundabout 54.952193 Darkness - lights lit Gateshead Gateshead -1.554247 1 2 0.0 5.0 Wet or damp Roundabout None 30.0 22:45 Urban Fine no high winds No 0

4.2. Esplorazione delle features¶

Da questo momento in poi si porrà l'attenzione sul valore target da predire, ossia Accident_Severity, e sulla relazione che quest'ultimo ha con le altre features.
Si procede alla visualizzazione dei dati per individuare i fattori che influenzano la classe target, ovvero la gravità dell'incidente. Così facendo si inizierà a capire l'utilità delle features in merito l'obiettivo del task.

Si definisce una funzione che servirà per plottare diagrammi a barre utili per visualizzare il legame di alcune features con Accident_Severity:

In [ ]:
def plotBar(feature, x=10, y=5, vertical=False, percent=False, order=False):
    f = plt.figure(figsize=(x,y))

    df = train_set.groupby(feature, as_index=False).agg(
        Accident_Severity=("Accident_Severity","mean"), 
        Number_of_Accidents=("Accident_Severity","size")
    )
    #df.columns = [feature, "Accident_Severity", "Number_of_Accidents"]
    if not order:
        df.sort_values(by=["Accident_Severity"], inplace=True, ascending=False)
        col = "rocket"
    else:
        col = "rocket_r"

    if percent:
        percentage = np.array(df[["Number_of_Accidents"]]/train_set.shape[0] * 100)

    if not vertical:
        plot = sns.barplot(data=df, x=feature, y='Accident_Severity', palette=col, order=df[feature])
        plot.get_yaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: '{:.2f}'.format(x, ',')))
        if percent:
            patches = plot.patches
            for i in range(len(patches)):
                x = patches[i].get_x() + patches[i].get_width()/2
                y = patches[i].get_height()+0.001
                plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)
    else:
        plot = sns.barplot(data=df, x="Accident_Severity", y=feature, palette=col, order=df[feature])
        plot.get_xaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: '{:.2f}'.format(x, ',')))
        if percent:
            patches = plot.patches
            for i in range(len(patches)):
                y = patches[i].get_y() + patches[i].get_height()/1.5
                x = patches[i].get_width()+0.025
                plot.annotate('{:.1f}%'.format(float(percentage[i])), (x, y), ha='center', fontsize=14)

Da questo punto si possono dividere le analisi delle features in relazione con la classe target, in 8 categorie: numeri, strada, tempo, posizione, condizioni incidente, condizioni veicolo, guidatore e veicolo. Queste analisi saranno distinte per tipo di attributi: categorici e numerici.

4.2.1. Esplorazione attributi categorici¶

In [ ]:
categorical = [var for var in train_set.columns if train_set[var].dtype=='O']
print('Ci sono {} attributi categorici\n'.format(len(categorical)))
print('Gli attributi categorici sono:\n\n', categorical)
Ci sono 30 attributi categorici

Gli attributi categorici sono:

 ['Age_Band_of_Driver', 'Driver_Home_Area_Type', 'Hit_Object_in_Carriageway', 'Hit_Object_off_Carriageway', 'Journey_Purpose_of_Driver', 'Junction_Location', 'make', 'Propulsion_Code', 'Sex_of_Driver', 'Skidding_and_Overturning', 'Towing_and_Articulation', 'Vehicle_Leaving_Carriageway', 'Vehicle_Manoeuvre', 'Vehicle_Type', 'Was_Vehicle_Left_Hand_Drive', 'X1st_Point_of_Impact', '1st_Road_Class', 'Carriageway_Hazards', 'Day_of_Week', 'Junction_Detail', 'Light_Conditions', 'Local_Authority_(District)', 'Local_Authority_(Highway)', 'Road_Surface_Conditions', 'Road_Type', 'Special_Conditions_at_Site', 'Time', 'Urban_or_Rural_Area', 'Weather_Conditions', 'InScotland']
In [ ]:
train_set[categorical].head()
Out[ ]:
Age_Band_of_Driver Driver_Home_Area_Type Hit_Object_in_Carriageway Hit_Object_off_Carriageway Journey_Purpose_of_Driver Junction_Location make Propulsion_Code Sex_of_Driver Skidding_and_Overturning Towing_and_Articulation Vehicle_Leaving_Carriageway Vehicle_Manoeuvre Vehicle_Type Was_Vehicle_Left_Hand_Drive X1st_Point_of_Impact 1st_Road_Class Carriageway_Hazards Day_of_Week Junction_Detail Light_Conditions Local_Authority_(District) Local_Authority_(Highway) Road_Surface_Conditions Road_Type Special_Conditions_at_Site Time Urban_or_Rural_Area Weather_Conditions InScotland
0 46 - 55 Rural None None Journey as part of work Entering roundabout MINI Petrol Female None No tow/articulation Did not leave carriageway Turning right Car No Did not impact A None Tuesday Roundabout Daylight Newcastle-under-Lyme Staffordshire Wet or damp Roundabout None 07:40 Urban Raining no high winds No
1 46 - 55 Rural None None Not known Approaching junction or waiting/parked at junc... RENAULT Petrol Female None No tow/articulation Did not leave carriageway Turning right Car No Nearside C None Wednesday Private drive or entrance Daylight Edinburgh, City of Edinburgh, City of Dry Single carriageway None 16:10 Urban Fine no high winds Yes
2 56 - 65 Urban area None None Commuting to/from work Mid Junction - on roundabout or on main road TOYOTA Heavy oil Male None No tow/articulation Did not leave carriageway Turning right Car No Front A None Wednesday Crossroads Daylight Preston Lancashire Dry Single carriageway None 08:25 Urban Fine no high winds No
3 46 - 55 Urban area None None Commuting to/from work Entering main road RENAULT Heavy oil Male None No tow/articulation Did not leave carriageway Turning left Car No Front A None Wednesday Roundabout Daylight Barnet Barnet Wet or damp Single carriageway None 06:55 Urban Unknown No
4 21 - 25 Urban area None None Not known Entering roundabout VOLKSWAGEN Heavy oil Male None No tow/articulation Did not leave carriageway Going ahead other Car No Offside A None Wednesday Roundabout Darkness - lights lit Gateshead Gateshead Wet or damp Roundabout None 22:45 Urban Fine no high winds No
Strada:¶
In [ ]:
plotBar("1st_Road_Class", percent=True)

Le strade più pericolose sono le tipo B, A, C, ecc. Le Motorway sono le più sicure anche se ci si aspettava il contrario essendo quelle in cui i veicoli hanno meno restrizioni di velocità.

Si definiscono alcune variabili utili nel proseguo:

In [ ]:
colors = [
    "#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#000000", 
    "#800000", "#008000", "#000080", "#808000", "#800080", "#008080", "#808080", 
    "#C00000", "#00C000", "#0000C0", "#C0C000", "#C000C0", "#00C0C0", "#C0C0C0", 
    "#400000", "#004000", "#000040", "#404000", "#400040", "#004040", "#404040", 
    "#200000", "#002000", "#000020", "#202000", "#200020", "#002020", "#202020", 
    "#600000", "#006000", "#000060", "#606000", "#600060", "#006060", "#606060", 
    "#A00000", "#00A000", "#0000A0", "#A0A000", "#A000A0", "#00A0A0", "#A0A0A0", 
    "#E00000", "#00E000", "#0000E0", "#E0E000", "#E000E0", "#00E0E0", "#E0E0E0", 
]
ac2005 = accidents.loc[accidents["Year"] == 2005]
a, b, c, d = ac2005.Longitude.min(), ac2005.Longitude.max(), ac2005.Latitude.min(), ac2005.Latitude.max()
mymap = plt.imread("./img/uk.png")
box = [a+0.1,b+0.1, c, d-0.05]
In [ ]:
df_byR = pd.DataFrame(accidents['1st_Road_Class'].value_counts().sort_index()).reset_index()
df_byR.columns = ["1st_Road_Class", "Numero di incidenti"]
df_byR = df_byR.sort_values(ascending=False, by=["Numero di incidenti"])[0:20]

f = plt.figure(figsize=(8,12))

localAuth = df_byR["1st_Road_Class"].values

plt.imshow(mymap, alpha=1, zorder=0, extent=box)
plt.title("1st_Road_Class")
#for i in range(len(localAuth)):
i=0
acc_R = accidents.loc[accidents["1st_Road_Class"]=="Motorway"]
plt.scatter(
    acc_R.Longitude, acc_R.Latitude, 
    alpha=0.5, 
    s=1, 
    label = "B",
    c=colors[i]
)
plt.legend(markerscale=10)

plt.show()

In figura sono rappresentati tutti gli incidenti lungo le strade di tipo Motorway; come si vede sono le strade principali della nazione; e da quanto visto dall'analisi precedente sono più sicure delle altre.

In [ ]:
plotBar("Road_Type", percent=True)

Le strade a singola carreggiata sono le più pericolose, andando verso le rotatoie che giustamente sono meno pericolose visto che i veicoli transitano molto più lenatemente nei loro pressi.

Tempo:¶
In [ ]:
plotBar("Day_of_Week", percent=True)

Come prevedibile i giorni della settimana con incidenti più gravi sono quelli nel weekend.

Caratteristica interessante è la fascia oraria, infatti la maggior parte degli incidenti gravi avvengono di notte. Per tale ragione viene creato un nuovo campo (Time_Interval) a partire dal campo Time, grazie alla classe custom creata per il transformer:

In [ ]:
class TimeTransformer(BaseEstimator, TransformerMixin):
    def __init__(self):
        pass

    def transform(self,X,y=None):
        X2 = X.copy()
        X2['Time_Interval'] = pd.to_datetime(X2['Time'])
        X2['Time_Interval'] = X2['Time_Interval'].dt.strftime('%H')
        return X2.drop('Time', axis=1)
        
    def fit(self, X, y=None):
        return self

#pipeline = Pipeline([
#    ("timeTransf", TimeTransformer())
#])
In [ ]:
print("Prima -->", train_set.shape)
timeTransformer = TimeTransformer()
train_set = timeTransformer.fit_transform(train_set)
print("Dopo -->", train_set.shape)
Prima --> (332578, 43)
Dopo --> (332578, 43)
In [ ]:
plotBar("Time_Interval", percent=True, x=20, order=True)

Per avere una visione più chiara, viene mostrato un grafico a linea, in cui l'inizio sull'asse x sono le ore 08:00.

In [ ]:
df = train_set.groupby('Time_Interval', as_index=False).agg(
        Accident_Severity=("Accident_Severity","mean"), 
        Number_of_Accidents=("Accident_Severity","size")
    )
df = df.reindex((df.index+8)%24)
plt.plot(df.Time_Interval, df.Accident_Severity, "-o")
plt.show()

Anche le ore della giornata giorno erano prevedibili, infatti gli incidenti più gravi avvengono durante la notte; come si vede dal grafico verso la notte la gravità degli incidenti aumenta. Il picco si trova alle 03:00 di notte.

Posizione:¶
In [ ]:
f = plt.figure(figsize=(16,12))
ax = plt.subplot2grid((1, 2), (0,0))

df = train_set.groupby("Local_Authority_(District)", as_index=False).agg({"Latitude": np.mean, "Longitude": np.mean, "Accident_Severity": np.mean})
df.columns = ["Local_Authority_(District)","Latitude", "Longitude", "Accident_Severity"]
df.sort_values(by=["Accident_Severity"], inplace=True, ascending=False)

ax.imshow(mymap, alpha=1, zorder=0, extent=box)
plt.title("Local_Authority_(District)")

cm = plt.get_cmap("jet")
sc = ax.scatter(df["Longitude"], df["Latitude"], c=df["Accident_Severity"], vmin=0, vmax=1, s=35, cmap=cm)
plt.colorbar(sc,fraction=0.052)

ax2 = plt.subplot2grid((1, 2), (0,1))

df = train_set.groupby("Local_Authority_(Highway)", as_index=False).agg({"Latitude": np.mean, "Longitude": np.mean, "Accident_Severity": np.mean})
df.columns = ["Local_Authority_(Highway)","Latitude", "Longitude", "Accident_Severity"]
df.sort_values(by=["Accident_Severity"], inplace=True, ascending=False)

ax2.imshow(mymap, alpha=1, zorder=0, extent=box)
plt.title("Local_Authority_(Highway)")

cm = plt.get_cmap("jet")
sc = ax2.scatter(df["Longitude"], df["Latitude"], c=df["Accident_Severity"], vmin=0, vmax=1, s=35, cmap=cm)
plt.colorbar(sc,fraction=0.052)

plt.show()

In figura sono rappresentate le contee con la relativa gravità degli incidenti.

Condizioni incidente:¶
In [ ]:
plotBar("Weather_Conditions", vertical=True, percent=True)

Le condizioni atmosferiche influiscono sulla pericolosità dell'incidente; infatti gli incidenti più gravi avvengono quando c'è nebbia e foschia.

In [ ]:
plotBar("Light_Conditions", percent=True, x=12)

Le condizioni di luce anche sono molto importanti per stabilire la pericolosità di un incidente; infatti gli incidenti più pericolosi avvengono quando è presente poca luce (Darkness - no lighting).

In [ ]:
plotBar("Road_Surface_Conditions", percent=True)

Le condizioni della strada pure sono importanti; gli incidenti più gravi avvengono in strade con allagamento con acqua alta sopra i 3 cm.

In [ ]:
plotBar("Carriageway_Hazards", vertical=True, percent=True)

Anche i pericoli presenti sulla strada influiscono sulla gravità dell'incidente; come si vede laddove è presente già un incidente, si hanno incidenti più pericolosi.

In [ ]:
plotBar("Special_Conditions_at_Site", vertical=True, percent=True)

Le condizioni speciali sono un altro attributo interessante, come si vede, si ha una maggiore pericolosità se la superficie stradale presenta dei difetti, seguita da una strada in cui è presente del liquido come benzina.

In [ ]:
plotBar("Hit_Object_in_Carriageway", percent=True, vertical=True)
In [ ]:
plotBar("Hit_Object_off_Carriageway", percent=True, vertical=True)

Altri attributi da poter tenere in considerazione sono gli oggetti colpiti in lungo la strada sia fuori che all'interno.

In [ ]:
plotBar("Skidding_and_Overturning", percent=True, vertical=True)
In [ ]:
plotBar("Vehicle_Leaving_Carriageway", percent=True, vertical=True) 
In [ ]:
plotBar("Vehicle_Manoeuvre", percent=True, vertical=True, y=8)  
Guidatore:¶
In [ ]:
plotBar("Age_Band_of_Driver", percent=True)

L'intervallo di età del conducente, come si vede, è un attributo che determina molto la gravità dell'incidente; infatti dice che incidenti di bambini e anziani sono i più gravi.
Da notare come ci sono due gruppi (0 - 5) e (6 - 10) che hanno un valore di gravità di incidente elevato, proprio pari ad 1! Solamente che nel training set rappresentano lo 0% circa delle istanze (outliers). Per capire meglio il numero di righe per queste due categorie:

In [ ]:
train_set['Age_Band_of_Driver'].value_counts()
Out[ ]:
26 - 35    71591
36 - 45    68736
46 - 55    56279
21 - 25    40779
56 - 65    34204
16 - 20    29642
66 - 75    18323
Over 75    12917
11 - 15       99
6 - 10         6
0 - 5          2
Name: Age_Band_of_Driver, dtype: int64

Si nota come sono presenti pochissime righe, ovvero 3 righe per la fascia di età (6 - 10), e una sola riga per (0 - 5). Per tale motivo verrano eliminate queste due fasce d'età (la classe che effettua la trasformazione per il test set è stata definita già in precendenza e si chiama RowsDropperTransformer):

In [ ]:
print("Prima -->", train_set.shape)
rowsAgeDriverDropper = RowsDropperTransformer('Age_Band_of_Driver')
train_set = rowsAgeDriverDropper.fit_transform(train_set)
print("Dopo -->", train_set.shape)
Prima --> (332578, 43)
Dopo --> (332570, 43)

Ecco come appare il nuovo plotBar riguardo le fasce età del conducente:

In [ ]:
plotBar("Age_Band_of_Driver", percent=True)
In [ ]:
plotBar("Sex_of_Driver", percent=True) 

Si nota come i guidatori maschi sono stati coinvolti o hanno causato incidenti più gravi delle donne.

Veicolo:¶
In [ ]:
plotBar("Vehicle_Type", percent=True, vertical=True, y=8) 

Il tipo di veicolo può influire sulla gravità dell'incidente; esempio il taxi è più sicuro di una moto con più di 500 cavalli.

4.2.2. Esplorazione attributi numerici¶

In [ ]:
numerical = [var for var in train_set.columns if train_set[var].dtype!='O']
print('Ci sono {} attributi numerici\n'.format(len(numerical)))
print('Gli attributi numerici sono\n\n', numerical)
Ci sono 13 attributi numerici

Gli attributi numerici sono

 ['Age_of_Vehicle', 'Engine_Capacity_.CC.', 'Vehicle_Location.Restricted_Lane', 'Year', 'Did_Police_Officer_Attend_Scene_of_Accident', 'Latitude', 'Longitude', 'Number_of_Casualties', 'Number_of_Vehicles', 'Pedestrian_Crossing-Human_Control', 'Pedestrian_Crossing-Physical_Facilities', 'Speed_limit', 'Accident_Severity']
Numeri:¶
In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Number_of_Casualties', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Number_of_Casualties'>
In [ ]:
plotBar("Number_of_Casualties", order=True, x=15)

Un attributo importante è il numero di morti, infatti all'aumentare di quest'ultimo aumenta la gravità dell'incidente come si vede nel grafico.
Stessa cosa si può dire per il numero di veicoli coinvolti:

In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Number_of_Vehicles', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Number_of_Vehicles'>
In [ ]:
plotBar("Number_of_Vehicles", percent=True, x=15, order=True)

Si nota inoltre come nella maggior parte degli incidenti (78,7%) sono coinvolti 2 incidenti. Seguito dal 14,9% un solo veicolo, e così via all'aumentare del numero di veicoli la probabilità dell'evento diminuisce.

In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Speed_limit', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Speed_limit'>
In [ ]:
plotBar("Speed_limit", percent=True, order=True)

Per quanto riguarda i limiti di velocità, come ci si aspettava, le strade con limiti di velocità più alti sono più pericolose.
Da notare come ci sono i limiti di velocità 10 e 15 che hanno un valore di gravità di incidente elevato, proprio pari ad 1! Solamente che nel training set rappresentano lo 0% circa delle istanze (outliers). Per capire meglio il numero di righe per questi due limiti di velocità:

In [ ]:
train_set['Speed_limit'].value_counts()
Out[ ]:
30.0    194141
60.0     64206
40.0     32305
70.0     22811
50.0     14342
20.0      4759
15.0         3
10.0         3
Name: Speed_limit, dtype: int64

Sono presenti pochissime righe, ovvero 3 righe per il limite di velocità 10, e 3 righe per 15. Per tale motivo verrano eliminate questi limiti (la classe che effettua la trasformazione per il test set è stata definita già in precendenza e si chiama RowsDropperTransformer):

In [ ]:
print("Prima -->", train_set.shape)
rowsSpeedLimitDropper = RowsDropperTransformer('Speed_limit')
train_set = rowsSpeedLimitDropper.fit_transform(train_set)
print("Dopo -->", train_set.shape)
Prima --> (332570, 43)
Dopo --> (332564, 43)

Ecco di seguito il nuovo diagramma a barre; si vede come adesso è più simile a come ci si aspettava, ovvero all'aumentare del limite di velocità, la gravità dell'incidente aumenta:

In [ ]:
plotBar("Speed_limit", percent=True, order=True)
In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Year', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Year'>
In [ ]:
plotBar("Year", percent=True, x=15, y=7, order=True)

L'anno in cui è avvenuto l'incidente può essere anche interessante, si nota un leggero incremento della gravità degli incidenti negli anni

Posizione:¶
In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Latitude', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Latitude'>
In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Longitude', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Longitude'>
In [ ]:
f = plt.figure(figsize=(16,12))

plt.title("1st_Road_Class")

ax = plt.subplot2grid((1, 2), (0,0))
ax.imshow(mymap, alpha=1, zorder=0, extent=box)

acc_R = train_set.loc[train_set["Accident_Severity"]==1]
ax.scatter(
    acc_R.Longitude, acc_R.Latitude, 
    alpha=0.5, 
    s=1, 
    label = "Severe",
    c='red', 
)
ax.legend(markerscale=10)

ax2 = plt.subplot2grid((1, 2), (0,1))
ax2.imshow(mymap, alpha=1, zorder=0, extent=box)
acc_R = train_set.loc[train_set["Accident_Severity"]==0]
ax2.scatter(
    acc_R.Longitude, acc_R.Latitude, 
    alpha=0.5, 
    s=1, 
    label = "Slight",
    c='green', 
)
ax2.legend(markerscale=10)

plt.show()

A confronto sono le mappe degli incidenti Servere e Slight; in realtà non si notano ad occhio dei posti che sono più gravi di altri. Però tali pattern potrebbero essere scoperti da un classificatore, perciò le due features verranno lasciate nel dataset finale.

Veicolo:¶
In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Age_of_Vehicle', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Age_of_Vehicle'>
In [ ]:
plotBar("Age_of_Vehicle", percent=False, x=20, y=10, order=True)

Come si nota, l'attributo Age_of_Vehicle è importante perchè all'aumentare dell'età del veicolo aumenta la gravità dell'incidente. Nel dettaglio si nota come la gravità dell'incidente rimane costante per veicoli con età che va da 1 a 14 anni. Dai 15 anni in su aumenta la gravità dell'incidente.

In [ ]:
sns.boxplot(x = 'Accident_Severity', y = 'Engine_Capacity_.CC.', data = train_set, palette='rocket')
Out[ ]:
<AxesSubplot:xlabel='Accident_Severity', ylabel='Engine_Capacity_.CC.'>
In [ ]:
from sklearn.preprocessing import KBinsDiscretizer
est = KBinsDiscretizer(n_bins=50, encode='ordinal', strategy='uniform')
train_set['Engine_Capacity_.CC._Bin'] = est.fit_transform(train_set[['Engine_Capacity_.CC.']])
plotBar("Engine_Capacity_.CC._Bin", percent=False, x=30, y=10, order=True)
train_set.drop(labels='Engine_Capacity_.CC._Bin', axis=1, inplace=True)

Si nota anche come il numero di cavalli influisce sulla gravità dell'incidente. In figura sono stati raggrupati il numero di cavalli in 50 intervalli per una questione di visibilità. All'aumentare del numero di cavalli aumenta la gravità dell'incidente.

Correlazione:¶

A questo punto arrivati, si effettua un'analisi della correlazione tra gli attributi numerici del training set:

In [ ]:
fig,ax = plt.subplots(figsize=(20,10))
fig = sns.heatmap(train_set.corr(), annot=True)
plt.title("Dataset correlations", size=20)
plt.show()

Si nota una lieve correlazione positiva tra numero di morti e numero di veicoli coinvolti (pari a 0,21); è presente anche una correlazione negativa tra latitudine e longitudine (pari a -0,44), e tra Pedestrian_Crossing-Physical_Facilities e Speed_limit (pari a -0,21).
Tali attributi potrebbero essere scartati e mantenuto solo uno per ciascuna coppia, ma per il momento verranno lasciati perchè più avanti nel caso verranno eliminati se il RandomForest non li riterrà features importanti.
Per quanto riguarda invece l'attributo target (Accident_Severity) si nota che ha bassi valori di correlazione lineare con gli altri attributi numerici. Il valore più alto di correlazione è presente con l'attributo Did_Police_Offer_Attend_Scene_of_Accident (pari a -0.16). Però nessuno ci dice che è presente un qualche altra forma di correlazione che non sia lineare.

4.2.3. Feature Selection¶

In questa sezione si eseguirà l'algoritmo del RandomForest sul training set per avere la lista ordinate degli attributi in ordine di importanza, e nel caso poter eliminare quelli che hanno un valore di importanza non rilevante. Per fare ciò verranno momentaneamente codificati gli attributi in label numeriche, in modo da rendere anche gli attributi categorici in formato numerico e quindi accettabile dall'algoritmo.

In [ ]:
Encoder_df = LabelEncoder() 
df = train_set.copy()

for c in df.columns:
    df[c] = Encoder_df.fit_transform(df[c])
In [ ]:
from sklearn.ensemble import RandomForestClassifier

filename = './models/rnd_clf_feature_importance_2.sav'

dfX = df.drop('Accident_Severity', axis = 1)
dfy = df['Accident_Severity']

# rnd_clf = RandomForestClassifier(n_estimators=40, n_jobs=-1, random_state=40)
# rnd_clf.fit(dfX, dfy)
# save the model to disk
# pickle.dump(rnd_clf, open(filename, 'wb'))

#load the model from disk
rnd_clf = pickle.load(open(filename, 'rb'))

attributes = dfX.columns
importances = rnd_clf.feature_importances_
index = np.argsort(importances)

plt.figure(figsize=(20,15))
plt.title("Attribute importance")
p = plt.barh(range(len(index)), importances[index], color='r', align='center')
plt.yticks(range(len(index)), attributes[index])
plt.xlabel("Relative importance")
plt.show()

Si nota come l'attributo più importante è Number_of_Vehicles seguito da EngineCapacity.CC. (cilindrata veicolo) e della posizione dell'incidente, in particolare latitudine e longitudine; segue anche il distretto di autorità locale. Anche l'orario in cui è avvenuto l'incidente, seguito dall'età del veicolo e dalla marca sono altrettanto importanti. Mentre si nota che l'attributo meno significativo è il Was_Vehicle_Left_Hand_Drive seguito da altri come InScotland e così via.
Comunque si può affermare che molti di questi risulatati erano stati già ottenuti esplorando i singoli attributi in funzione dell'attributo target.

Dato che il numero di attributi è elevato, si rimuovono tutti gli attributi che sono meno importanti dell'attributo Weather_Conditions; ovvero rimarranno solamente 24 feature totali più quella target (25 features):

In [ ]:
print("Prima -->", train_set.shape)
train_set.drop(labels='Was_Vehicle_Left_Hand_Drive', axis=1, inplace=True)
train_set.drop(labels='InScotland', axis=1, inplace=True)
train_set.drop(labels='Pedestrian_Crossing-Human_Control', axis=1, inplace=True)
train_set.drop(labels='Towing_and_Articulation', axis=1, inplace=True)
train_set.drop(labels='Vehicle_Location.Restricted_Lane', axis=1, inplace=True)
train_set.drop(labels='Carriageway_Hazards', axis=1, inplace=True)
train_set.drop(labels='Special_Conditions_at_Site', axis=1, inplace=True)
train_set.drop(labels='Sex_of_Driver', axis=1, inplace=True)
train_set.drop(labels='Skidding_and_Overturning', axis=1, inplace=True)
train_set.drop(labels='Hit_Object_off_Carriageway', axis=1, inplace=True)
train_set.drop(labels='Propulsion_Code', axis=1, inplace=True)
train_set.drop(labels='Road_Surface_Conditions', axis=1, inplace=True)
train_set.drop(labels='Light_Conditions', axis=1, inplace=True)
train_set.drop(labels='Driver_Home_Area_Type', axis=1, inplace=True)
train_set.drop(labels='Vehicle_Leaving_Carriageway', axis=1, inplace=True)
train_set.drop(labels='Pedestrian_Crossing-Physical_Facilities', axis=1, inplace=True)
train_set.drop(labels='Hit_Object_in_Carriageway', axis=1, inplace=True)
train_set.drop(labels='Urban_or_Rural_Area', axis=1, inplace=True)
print("Dopo -->", train_set.shape)
Prima --> (332564, 43)
Dopo --> (332564, 25)

4.3. Data Transformation¶

In questa fase, si preparano i dati da passare ai modelli di machine learning.

In [ ]:
train_set_copy_2 = train_set.copy()

4.3.1. Dimensionality:¶

In questa parte verrà analizzata la dimensionalità degli attributi categorici, e laddove qualche attributi presenta un elevato numero di valori univoci, si attueranno delle tecniche di riduzione della dimensionalità.

In [ ]:
categorical = [var for var in train_set.columns if train_set[var].dtype=='O']

plt.figure(figsize=(15,5))
s = pd.Series(train_set[categorical].nunique()).sort_values(ascending=False)
plot = sns.barplot(x = s.index, y = s.values)
plt.xticks(rotation=90)
plt.title("Dimensionalità attributi categorici", size=20)

patches = plot.patches
for i in range(len(patches)):
    x = patches[i].get_x() + patches[i].get_width()/2
    y = patches[i].get_height()+5
    plot.annotate('{:.0f}'.format(float(s.values[i])), (x, y), ha='center', fontsize=14)

Come si vede, gli attributi con maggiore dimensionalità sono: LocalAuthority(District), make e LocalAuthority(Highway).

4.3.1.1. *Local_Authority_(District)*:¶

Iniziando ad analizzare LocalAuthority(District), quest'attributo presenta 416 valori univoci. Per ridurre la dimensionalità di tale attributi una possibile via è quella di raggruppare più local authority in base alla loro poszione giografica. Verrà adottata la tecnica di clustering Kmeans, e verrà scelto un numero di cluster pari a 20.
Ecco di seguito il dataframe che rappresenta ogni distretto di autorità con rispettive coordinate geografiche (media delle cordinate geografiche presenti nel dataset relative a quel distretto di autorità) e con la gravità dell'incidente (media delle gravità degli incidenti presenti nel dataset relative a quel distretto di autorità):

In [ ]:
df = train_set.groupby('Local_Authority_(District)', as_index=False).agg(
        Latitude=("Latitude","mean"), 
        Longitude=("Longitude","mean"),
        Accident_Severity=("Accident_Severity","mean")
)
df.sort_values(by=["Accident_Severity"], inplace=True, ascending=False)
df.head()
Out[ ]:
Local_Authority_(District) Latitude Longitude Accident_Severity
1 Aberdeenshire 57.287628 -2.335154 0.787259
288 Ryedale 54.178870 -0.837545 0.759931
264 Powys 52.326763 -3.362590 0.742601
293 Scottish Borders 55.612100 -2.745900 0.738095
277 Richmondshire 54.382432 -1.788395 0.735537
In [ ]:
X = df[['Longitude', 'Latitude']]
X
Out[ ]:
Longitude Latitude
1 -2.335154 57.287628
288 -0.837545 54.178870
264 -3.362590 52.326763
293 -2.745900 55.612100
277 -1.788395 54.382432
... ... ...
226 -2.261118 53.023136
349 -1.682462 52.624785
196 -1.833053 52.677878
29 -1.555640 55.096479
338 -2.169440 53.016737

416 rows × 2 columns

A questo punto viene applicato il metodo Kmeans clustering su tale insieme di punti (coordinate dei distretti di autorità); e viene appesa una nuova colonna chiamata cluster, che indicherà il relativo cluster di appartenenza per lo specifico distretto di autorità:

In [ ]:
X = df[['Longitude', 'Latitude']]
k = 20
kmeans = KMeans(n_clusters=k, random_state=42)
kmeans.fit(X)
df['cluster'] = kmeans.predict(X)
#df['cluster'] = df['cluster'].apply(str)
df
Out[ ]:
Local_Authority_(District) Latitude Longitude Accident_Severity cluster
1 Aberdeenshire 57.287628 -2.335154 0.787259 6
288 Ryedale 54.178870 -0.837545 0.759931 7
264 Powys 52.326763 -3.362590 0.742601 14
293 Scottish Borders 55.612100 -2.745900 0.738095 15
277 Richmondshire 54.382432 -1.788395 0.735537 7
... ... ... ... ... ...
226 Newcastle-under-Lyme 53.023136 -2.261118 0.316558 9
349 Tamworth 52.624785 -1.682462 0.297189 1
196 Lichfield 52.677878 -1.833053 0.282486 1
29 Blyth Valley 55.096479 -1.555640 0.275862 7
338 Stoke-on-Trent 53.016737 -2.169440 0.251445 9

416 rows × 5 columns

I seguenti sono i centroidi dei cluster:

In [ ]:
centers = pd.DataFrame(kmeans.cluster_centers_, columns=['Longitude','Latitude'])
centers
Out[ ]:
Longitude Latitude
0 -4.383417 55.781044
1 -2.107605 52.365486
2 0.597531 51.339690
3 -3.021169 54.266435
4 -4.428715 50.485996
5 -1.058310 51.168564
6 -3.030824 57.711439
7 -1.522897 54.811662
8 -0.979817 52.367081
9 -2.543715 53.403071
10 -2.181056 51.121147
11 -4.222353 52.561097
12 -1.388832 53.300638
13 -0.211900 51.520149
14 -3.294493 51.445339
15 -3.229972 56.101788
16 -0.295232 53.189273
17 1.040367 52.360163
18 -6.706904 57.976669
19 -1.234547 60.186866

Di seguito verrà mostrato il diagramma di Voronoi, in cui sono rappresentati i diversi centroidi sulla mappa geografica con i relativi distrtti di autorità e anche i confini di decisione:

In [ ]:
def plot_data(X):
    plt.plot(X[:, 0], X[:, 1], 'k.', markersize=5)

def plot_centroids(centroids, weights=None, circle_color='w', cross_color='blue'):
    if weights is not None:
        centroids = centroids[weights > weights.max() / 10]
    plt.scatter(centroids[:, 0], centroids[:, 1],
                marker='o', s=17, linewidths=10,
                color=circle_color, zorder=10, alpha=0.9)
    plt.scatter(centroids[:, 0], centroids[:, 1],
               marker='x', s=15, linewidths=10,
               color=cross_color, zorder=11, alpha=1)

def plot_decision_boundaries(clusterer, X, resolution=1000, show_centroids=True,
                             show_xlabels=True, show_ylabels=True):
    mins = X.min(axis=0) - 0.1
    maxs = X.max(axis=0) + 0.1
    xx, yy = np.meshgrid(np.linspace(mins[0], maxs[0], resolution),
                         np.linspace(mins[1], maxs[1], resolution))
    Z = clusterer.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    plt.imshow(mymap, alpha=1, zorder=0, extent=box)
    plt.contourf(Z, extent=(mins[0], maxs[0], mins[1], maxs[1]),
                cmap="Pastel2", alpha=0.4)
    plt.contour(Z, extent=(mins[0], maxs[0], mins[1], maxs[1]),
                linewidths=1, colors='k')
    plot_data(X)
    
    if show_centroids:
        plot_centroids(clusterer.cluster_centers_)

    if show_xlabels:
        plt.xlabel("$x_1$", fontsize=14)
    else:
        plt.tick_params(labelbottom=False)
    if show_ylabels:
        plt.ylabel("$x_2$", fontsize=14, rotation=0)
    else:
        plt.tick_params(labelleft=False)
In [ ]:
plt.figure(figsize=(8, 10))
plot_decision_boundaries(kmeans, X.to_numpy())
plt.show()

Nella seguente figura sono riportati a sinistra i LocalAuthority(District), mentre a destra i rispettivi centroidi calcolati con l'algoritmo kmeans. Da notare i colori dei punti che rappresentano il valore di gravità dell'incidente:

In [ ]:
f = plt.figure(figsize=(16,12))
ax = plt.subplot2grid((1, 2), (0,1))

df2 = df.groupby('cluster', as_index=False).agg(
        Accident_Severity=("Accident_Severity","mean")
)
df2 = df2.join(centers)

ax.imshow(mymap, alpha=1, zorder=0, extent=box)
plt.title("Centroidi")

cm = plt.get_cmap("jet")
sc = ax.scatter(df2["Longitude"], df2["Latitude"], vmin=0, vmax=1, s=250, cmap=cm, c=df2["Accident_Severity"])
plt.colorbar(sc,fraction=0.052)

for index, row in df2.iterrows():
    ax.annotate('{:.0f}'.format(row['cluster']), (row['Longitude']-0.03, row['Latitude']-0.1), ha='center', fontsize=12)

ax2 = plt.subplot2grid((1, 2), (0,0))

ax2.imshow(mymap, alpha=1, zorder=0, extent=box)
plt.title("Local_Authority_(District)")

cm = plt.get_cmap("jet")
sc = ax2.scatter(df["Longitude"], df["Latitude"], c=df["Accident_Severity"], vmin=0, vmax=1, s=35, cmap=cm)
plt.colorbar(sc,fraction=0.052)

plt.show()

A questo punto non resta che sostituire nel dataset i LocalAuthority(District) con i rispettivi centroidi così facendo, finalmente si ridurrà la dimensionalità di tale attributo da 416 valori univoci a ben soli 20 valori univoci.
Per fare ciò si costruirà un dizionario in cui la chiave rappresenta il nome del distretto di autorità, mentre il valore è il rispettivo centroide a cui esso è associato.
Per fare più veloce, di seguito viene crato un transformer che racchiude tutti questi passaggi (dal kmeans al replace), che servirà in seguito anche per modificare il test set:

In [ ]:
class ClusteringReplaceValuesTransformer(BaseEstimator, TransformerMixin):
    def __init__(self, feature, k=20):
        self.feature=feature
        self.k=k

    def transform(self,X,y=None):
        X2 = X.copy()
        X2[self.feature].replace(self.dict, inplace=True)
        return X2

    def fit(self, X, y=None):
        df = X.groupby(self.feature, as_index=False).agg(
                                Latitude=("Latitude","mean"), 
                                Longitude=("Longitude","mean"),
                                Accident_Severity=("Accident_Severity","mean")
        )
        df.sort_values(by=["Accident_Severity"], inplace=True, ascending=False)
        kmeans = KMeans(n_clusters=self.k, random_state=42)
        kmeans.fit(df[['Longitude', 'Latitude']])
        df['cluster'] = kmeans.predict(df[['Longitude', 'Latitude']])
        df['cluster'] = df['cluster'].apply(str)
        self.dict = df[[self.feature, 'cluster']].set_index(self.feature).to_dict()['cluster']
        return self

#pipeline = Pipeline([
#    ("replaceDistrict", ClusteringReplaceValuesTransformer("Local_Authority_(District)"))
In [ ]:
clusteringReplaceDistrict = ClusteringReplaceValuesTransformer("Local_Authority_(District)")
train_set = clusteringReplaceDistrict.fit_transform(train_set)
train_set['Local_Authority_(District)'].value_counts()
Out[ ]:
13    67801
12    36588
9     36219
1     26995
5     22203
2     20068
8     16436
7     15458
10    15073
16    13178
17    11643
14    11257
0     10477
15     8939
4      6717
3      6525
6      4036
11     2836
18       59
19       56
Name: Local_Authority_(District), dtype: int64

L'operazione è stata eseguita con successo; come si può notare il centroide più frequente è il numero 13, non a caso se si vede sulla cartina sopra, si nota come il centroide 13 è nei pressi di Londra.

4.3.1.2. *Local_Authority_(Highway)*:¶

Per quanto riguarda l'attributo LocalAuthority(Highway), si possono rieseguire gli stessi ed identici passaggi effettuati per l'attributo LocalAuthority(District):

In [ ]:
df = train_set.groupby('Local_Authority_(Highway)', as_index=False).agg(
        Latitude=("Latitude","mean"), 
        Longitude=("Longitude","mean"),
        Accident_Severity=("Accident_Severity","mean")
)
df.sort_values(by=["Accident_Severity"], inplace=True, ascending=False)
X = df[['Longitude', 'Latitude']]
k = 20
kmeans = KMeans(n_clusters=k, random_state=42)
kmeans.fit(X)
df['cluster'] = kmeans.predict(X)
centers = pd.DataFrame(kmeans.cluster_centers_, columns=['Longitude','Latitude'])
In [ ]:
plt.figure(figsize=(8, 10))
plot_decision_boundaries(kmeans, X.to_numpy())
plt.show()
In [ ]:
f = plt.figure(figsize=(16,12))
ax = plt.subplot2grid((1, 2), (0,1))

df2 = df.groupby('cluster', as_index=False).agg(
        Accident_Severity=("Accident_Severity","mean")
)
df2 = df2.join(centers)

ax.imshow(mymap, alpha=1, zorder=0, extent=box)
plt.title("Centroidi")

cm = plt.get_cmap("jet")
sc = ax.scatter(df2["Longitude"], df2["Latitude"], vmin=0, vmax=1, s=250, cmap=cm, c=df2["Accident_Severity"])
plt.colorbar(sc,fraction=0.052)

for index, row in df2.iterrows():
    ax.annotate('{:.0f}'.format(row['cluster']), (row['Longitude']-0.03, row['Latitude']-0.1), ha='center', fontsize=12)

ax2 = plt.subplot2grid((1, 2), (0,0))

ax2.imshow(mymap, alpha=1, zorder=0, extent=box)
plt.title("Local_Authority_(Highway)")

cm = plt.get_cmap("jet")
sc = ax2.scatter(df["Longitude"], df["Latitude"], c=df["Accident_Severity"], vmin=0, vmax=1, s=35, cmap=cm)
plt.colorbar(sc,fraction=0.052)

plt.show()

Per effettuare la replace dei valori si può usare lo stasso transformer creato in precedenza:

In [ ]:
clusteringReplaceHighway = ClusteringReplaceValuesTransformer("Local_Authority_(Highway)")
train_set = clusteringReplaceHighway.fit_transform(train_set)
train_set['Local_Authority_(Highway)'].value_counts()
Out[ ]:
5     73034
11    38369
16    30938
15    29354
1     27557
7     27324
19    17234
9     13970
2     13937
0     13047
10    11183
3     11140
12     8276
13     8072
4      3915
6      2637
18     1399
8      1063
14       59
17       56
Name: Local_Authority_(Highway), dtype: int64

Ancora una volta possiamo vedere come il centroide più frequente si trova nei pressi di Londra.

4.3.1.3. *make*:¶

Per quanto riguarda l'attributo make (marca del veicolo), si può vedere come molte marche di veicoli sono presenti in pochissimi incidenti. Per tale ragione si decide di scegliere una soglia del numero di incidenti (in questo caso si sceglierà 500 dopo avere fatto alcune prove), e di raggruppare tutte le marche di veicoli coinvolti in un numero di incidenti inferiore alla soglia in una nuova marca generale chiamata OTHERS. Così facendo si riduce di molto la dimensionalità dell'attributo make.
Viene creato un transformer che effettua tale operazione, che sarà usato all'interno di una pipeline:

In [ ]:
class VehiclesFeatureGroupNameTransformer(BaseEstimator, TransformerMixin):
    def __init__(self, threshold=500, groupName='OTHERS', feature='make'):
        self.threshold = threshold
        self.groupName = groupName
        self.feature = feature

    def transform(self,X,y=None):
        X2 = X.copy()
        X2.loc[~X[self.feature].isin(self.makeNotToRemove), self.feature] = self.groupName
        return X2

    def fit(self, X, y=None):
        self.makeNotToRemove = X.groupby(self.feature).filter(lambda x: len(x) >= self.threshold)[self.feature].unique()
        return self

#pipeline = Pipeline([
#    ("makeGroupOthers", vehiclesFeatureGroupNameTransformer())
#])
#train_set = VehiclesFeatureGroupNameTransformer().fit_transform(train_set)
In [ ]:
numMake = len(train_set.groupby('make')['make'].unique())
makeToRemove = train_set.groupby('make').filter(lambda x: len(x) < 500)['make'].unique()
makeGroupNameOthers = VehiclesFeatureGroupNameTransformer()
train_set = makeGroupNameOthers.fit_transform(train_set)
print("Veranno raggruppate", len(makeToRemove), "marche su un totale di", numMake, "marche.")
print("Rimangono", numMake-len(makeToRemove)+1, "marche.")
Veranno raggruppate 251 marche su un totale di 296 marche.
Rimangono 46 marche.
In [ ]:
plotBar("make", percent=True, vertical=True, y=15) 

Come prevedibile negli incidenti più gravi sono coinvolte maggiormente le moto, infatti le marche degli incidenti più gravi sono marche di motociclette, esempio: DUCATI, HARLEY-DAVIDSON, TRIUMPH, KAWASAKI, YAMAHA, APRILIA, SUZUKI, ecc. Ciò era prevedibile perchè le moto in caso di incidente sono molto meno sicure di un automobile.

4.3.2. Encoding:¶

In questa fase verranno convertiti tutti gli attributi categorici in numerici. Ecco di seguito la lista degli attributi categorici:

In [ ]:
categorical = [var for var in train_set.columns if train_set[var].dtype=='O']
train_set[categorical].head()
Out[ ]:
Age_Band_of_Driver Journey_Purpose_of_Driver Junction_Location make Vehicle_Manoeuvre Vehicle_Type X1st_Point_of_Impact 1st_Road_Class Day_of_Week Junction_Detail Local_Authority_(District) Local_Authority_(Highway) Road_Type Weather_Conditions Time_Interval
0 46 - 55 Journey as part of work Entering roundabout MINI Turning right Car Did not impact A Tuesday Roundabout 9 16 Roundabout Raining no high winds 07
1 46 - 55 Not known Approaching junction or waiting/parked at junc... RENAULT Turning right Car Nearside C Wednesday Private drive or entrance 15 12 Single carriageway Fine no high winds 16
2 56 - 65 Commuting to/from work Mid Junction - on roundabout or on main road TOYOTA Turning right Car Front A Wednesday Crossroads 9 7 Single carriageway Fine no high winds 08
3 46 - 55 Commuting to/from work Entering main road RENAULT Turning left Car Front A Wednesday Roundabout 13 5 Single carriageway Unknown 06
4 21 - 25 Not known Entering roundabout VOLKSWAGEN Going ahead other Car Offside A Wednesday Roundabout 7 0 Roundabout Fine no high winds 22

Come si può vedere ed intuire, gli unici attributi che presentano un ordine sono: Age_Band_of_Driver, Time_Interval e Day_of_Week. Per questi tre attributi la soluzione sarà quella di adottare la classe OrdinalEncoder per l'attributo Age_Band_of_Driver, mentre per gli altri due attributi temporali, dato che è presente anche un ordine circolare perchè le ore e i giorni della settimana si ripetono nel tempo, la soluzione scelta è quella di tener conto di questa caratteristica convertendoli come "punti" su un cerchio, rappresentati da due nuove colonne ciascuno, rappresentante il seno e il coseno dell'angolo. Infine per i restanti attributi, essendo non ordinali, si può adottare tranquillamente la classe OneHotEncoder.

4.3.2.1. *Age_Band_of_Driver*:¶
In [ ]:
ordinal_encoder_AgeDriver = OrdinalEncoder()
train_set[['Age_Band_of_Driver']] = ordinal_encoder_AgeDriver.fit_transform(train_set[['Age_Band_of_Driver']])
train_set[['Age_Band_of_Driver']]
Out[ ]:
Age_Band_of_Driver
0 5.0
1 5.0
2 6.0
3 5.0
4 2.0
... ...
332573 7.0
332574 3.0
332575 4.0
332576 4.0
332577 6.0

332564 rows × 1 columns

La trasformazione è stata eseguita, si vede di seguito le categorie come sono state ordinate:

In [ ]:
ordinal_encoder_AgeDriver.categories_
Out[ ]:
[array(['11 - 15', '16 - 20', '21 - 25', '26 - 35', '36 - 45', '46 - 55',
        '56 - 65', '66 - 75', 'Over 75'], dtype=object)]

Si nota come in automatico la classe OrdinalEncoder ha trovato il giusto ordinamento tra le categorie, infatti si nota un ordinamento crescente delle fasce di età nell'array.

4.3.2.2. *Day_of_Week* e *Time_Interval*:¶

Per i due attributi temporali (Day_of_Week e Time_Interval), il primo passaggio da compiere è trasformarli in numerici attraverso l'ausilio della classe OrdinalEncoder:

In [ ]:
ordinal_encoder_DayWeek = OrdinalEncoder(categories=[['Monday', 'Tuesday',  'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']])
train_set[['Day_of_Week']] = ordinal_encoder_DayWeek.fit_transform(train_set[['Day_of_Week']])
train_set[['Day_of_Week']]
Out[ ]:
Day_of_Week
0 1.0
1 2.0
2 2.0
3 2.0
4 2.0
... ...
332573 5.0
332574 3.0
332575 2.0
332576 6.0
332577 1.0

332564 rows × 1 columns

In [ ]:
ordinal_encoder_DayWeek.categories_
Out[ ]:
[array(['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
        'Sunday'], dtype=object)]
In [ ]:
ordinal_encoder_TimeInterval = OrdinalEncoder()
train_set[['Time_Interval']] = ordinal_encoder_TimeInterval.fit_transform(train_set[['Time_Interval']])
train_set[['Time_Interval']]
Out[ ]:
Time_Interval
0 7.0
1 16.0
2 8.0
3 6.0
4 22.0
... ...
332573 16.0
332574 13.0
332575 12.0
332576 13.0
332577 7.0

332564 rows × 1 columns

In [ ]:
ordinal_encoder_TimeInterval.categories_
Out[ ]:
[array(['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10',
        '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21',
        '22', '23'], dtype=object)]

Dopo aver controllato che la codifica è ordinata, si passa alla costruzione delle due nuove colonne ciascuna, che rappresentano il seno e coseno dei punti nell'angolo.
Le formule sono le seguenti:

Day_of_Week: $$ \sin{(\frac{d}{7}*2*\pi)} $$ $$ \cos{(\frac{d}{7}*2*\pi)} $$

Time_Interval:

$$ \sin{(\frac{t}{24}*2*\pi)} $$$$ \cos{(\frac{t}{24}*2*\pi)} $$
In [ ]:
print("Prima -->", train_set.shape)
train_set['Day_of_Week_sin'] = np.sin(train_set['Day_of_Week'] * (2 * np.pi / 7))
train_set['Day_of_Week_cos'] = np.cos(train_set['Day_of_Week'] * (2 * np.pi / 7))
train_set.drop(labels='Day_of_Week', axis=1, inplace=True)
print("Dopo -->", train_set.shape)
Prima --> (332564, 25)
Dopo --> (332564, 26)
In [ ]:
print("Prima -->", train_set.shape)
train_set['Time_Interval_sin'] = np.sin(train_set['Time_Interval'] * (2 * np.pi / 24))
train_set['Time_Interval_cos'] = np.cos(train_set['Time_Interval'] * (2 * np.pi / 24))
train_set.drop(labels='Time_Interval', axis=1, inplace=True)
print("Dopo -->", train_set.shape)
Prima --> (332564, 26)
Dopo --> (332564, 27)

Viene crato un transformer che esegue tutta queste serie di trasformazioni per tali attributi:

In [ ]:
class CircularEncoder(BaseEstimator, TransformerMixin):
    def __init__(self, feature='Time_Interval', cat=None, number=24):
        self.feature = feature
        self.cat = cat
        self.number = number
        
    def transform(self,X,y=None):
        if self.cat is None:
            self.ordinal_encoder = OrdinalEncoder()
        else: 
            self.ordinal_encoder = OrdinalEncoder(categories=self.cat)
        self.ordinal_feature = self.ordinal_encoder.fit_transform(X[[self.feature]])
        X2 = X.copy()
        X2[self.feature+'_sin'] = np.sin(self.ordinal_feature * (2 * np.pi / self.number))
        X2[self.feature+'_cos'] = np.cos(self.ordinal_feature * (2 * np.pi / self.number))
        X2.drop(labels=self.feature, axis=1, inplace=True)
        return X2

    def fit(self, X, y=None):
        return self
4.3.2.3. Altri attributi categorici:¶

Per tutti i restanti attributi categorici come detto in precedenza è possibile adottare la classe OneHotEncoder:

In [ ]:
print("Prima -->", train_set.shape)
categorical = [var for var in train_set.columns if train_set[var].dtype=='O']
cat_encoder = OneHotEncoder(sparse=False)
cat_encoder.fit(train_set[categorical])

def get_cat_encoder(df):
    temp_df = pd.DataFrame(data=cat_encoder.transform(df[categorical]), columns=cat_encoder.get_feature_names_out())
    df.drop(columns=categorical, axis=1, inplace=True)
    df = pd.concat([df.reset_index(drop=True), temp_df], axis=1)
    return df

train_set = get_cat_encoder(train_set)
print("Dopo -->", train_set.shape)
Prima --> (332564, 27)
Dopo --> (332564, 186)
In [ ]:
train_set.head()
Out[ ]:
Age_Band_of_Driver Age_of_Vehicle Engine_Capacity_.CC. Year Did_Police_Officer_Attend_Scene_of_Accident Latitude Longitude Number_of_Casualties Number_of_Vehicles Speed_limit Accident_Severity Day_of_Week_sin Day_of_Week_cos Time_Interval_sin Time_Interval_cos Journey_Purpose_of_Driver_Commuting to/from work Journey_Purpose_of_Driver_Journey as part of work Journey_Purpose_of_Driver_Not known Journey_Purpose_of_Driver_Other Journey_Purpose_of_Driver_Other/Not known (2005-10) Journey_Purpose_of_Driver_Pupil riding to/from school Journey_Purpose_of_Driver_Taking pupil to/from school Junction_Location_Approaching junction or waiting/parked at junction approach Junction_Location_Cleared junction or waiting/parked at junction exit Junction_Location_Entering from slip road Junction_Location_Entering main road Junction_Location_Entering roundabout Junction_Location_Leaving main road Junction_Location_Leaving roundabout Junction_Location_Mid Junction - on roundabout or on main road Junction_Location_Not at or within 20 metres of junction make_ALFA ROMEO make_APRILIA make_AUDI make_BMW make_CHEVROLET make_CHRYSLER make_CITROEN make_DAEWOO make_DAIHATSU make_DUCATI make_FIAT make_FORD make_HARLEY-DAVIDSON make_HONDA make_HYUNDAI make_IVECO make_JAGUAR make_KAWASAKI make_KIA make_KTM make_LAND ROVER make_LEXUS make_LONDON TAXIS INT make_MAZDA make_MERCEDES make_MG make_MINI make_MITSUBISHI make_NISSAN make_OTHERS make_PEUGEOT make_PIAGGIO make_RENAULT make_ROVER make_SAAB make_SEAT make_SKODA make_SMART make_SUBARU make_SUZUKI make_TOYOTA make_TRIUMPH make_VAUXHALL make_VOLKSWAGEN make_VOLVO make_YAMAHA Vehicle_Manoeuvre_Changing lane to left Vehicle_Manoeuvre_Changing lane to right Vehicle_Manoeuvre_Going ahead left-hand bend Vehicle_Manoeuvre_Going ahead other Vehicle_Manoeuvre_Going ahead right-hand bend Vehicle_Manoeuvre_Moving off Vehicle_Manoeuvre_Overtaking - nearside Vehicle_Manoeuvre_Overtaking moving vehicle - offside Vehicle_Manoeuvre_Overtaking static vehicle - offside Vehicle_Manoeuvre_Parked Vehicle_Manoeuvre_Reversing Vehicle_Manoeuvre_Slowing or stopping Vehicle_Manoeuvre_Turning left Vehicle_Manoeuvre_Turning right Vehicle_Manoeuvre_U-turn Vehicle_Manoeuvre_Waiting to go - held up Vehicle_Manoeuvre_Waiting to turn left Vehicle_Manoeuvre_Waiting to turn right Vehicle_Type_Agricultural vehicle Vehicle_Type_Bus or coach (17 or more pass seats) Vehicle_Type_Car Vehicle_Type_Electric motorcycle Vehicle_Type_Goods 7.5 tonnes mgw and over Vehicle_Type_Goods over 3.5t. and under 7.5t Vehicle_Type_Goods vehicle - unknown weight Vehicle_Type_Minibus (8 - 16 passenger seats) Vehicle_Type_Motorcycle - unknown cc Vehicle_Type_Motorcycle 125cc and under Vehicle_Type_Motorcycle 50cc and under Vehicle_Type_Motorcycle over 125cc and up to 500cc Vehicle_Type_Motorcycle over 500cc Vehicle_Type_Other vehicle Vehicle_Type_Taxi/Private hire car Vehicle_Type_Van / Goods 3.5 tonnes mgw or under X1st_Point_of_Impact_Back X1st_Point_of_Impact_Did not impact X1st_Point_of_Impact_Front X1st_Point_of_Impact_Nearside X1st_Point_of_Impact_Offside 1st_Road_Class_A 1st_Road_Class_A(M) 1st_Road_Class_B 1st_Road_Class_C 1st_Road_Class_Motorway 1st_Road_Class_Unclassified Junction_Detail_Crossroads Junction_Detail_Mini-roundabout Junction_Detail_More than 4 arms (not roundabout) Junction_Detail_Not at junction or within 20 metres Junction_Detail_Other junction Junction_Detail_Private drive or entrance Junction_Detail_Roundabout Junction_Detail_Slip road Junction_Detail_T or staggered junction Local_Authority_(District)_0 Local_Authority_(District)_1 Local_Authority_(District)_10 Local_Authority_(District)_11 Local_Authority_(District)_12 Local_Authority_(District)_13 Local_Authority_(District)_14 Local_Authority_(District)_15 Local_Authority_(District)_16 Local_Authority_(District)_17 Local_Authority_(District)_18 Local_Authority_(District)_19 Local_Authority_(District)_2 Local_Authority_(District)_3 Local_Authority_(District)_4 Local_Authority_(District)_5 Local_Authority_(District)_6 Local_Authority_(District)_7 Local_Authority_(District)_8 Local_Authority_(District)_9 Local_Authority_(Highway)_0 Local_Authority_(Highway)_1 Local_Authority_(Highway)_10 Local_Authority_(Highway)_11 Local_Authority_(Highway)_12 Local_Authority_(Highway)_13 Local_Authority_(Highway)_14 Local_Authority_(Highway)_15 Local_Authority_(Highway)_16 Local_Authority_(Highway)_17 Local_Authority_(Highway)_18 Local_Authority_(Highway)_19 Local_Authority_(Highway)_2 Local_Authority_(Highway)_3 Local_Authority_(Highway)_4 Local_Authority_(Highway)_5 Local_Authority_(Highway)_6 Local_Authority_(Highway)_7 Local_Authority_(Highway)_8 Local_Authority_(Highway)_9 Road_Type_Dual carriageway Road_Type_One way street Road_Type_Roundabout Road_Type_Single carriageway Road_Type_Slip road Road_Type_Unknown Weather_Conditions_Fine + high winds Weather_Conditions_Fine no high winds Weather_Conditions_Fog or mist Weather_Conditions_Other Weather_Conditions_Raining + high winds Weather_Conditions_Raining no high winds Weather_Conditions_Snowing + high winds Weather_Conditions_Snowing no high winds Weather_Conditions_Unknown
0 5.0 9.0 1598.0 2013 1.0 53.047616 -2.244131 1 2 40.0 0 0.781831 0.623490 0.965926 -2.588190e-01 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0
1 5.0 6.0 1998.0 2011 2.0 55.949146 -3.184619 1 2 30.0 0 0.974928 -0.222521 -0.866025 -5.000000e-01 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
2 6.0 3.0 2231.0 2011 1.0 53.762771 -2.740800 3 2 30.0 0 0.974928 -0.222521 0.866025 -5.000000e-01 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
3 5.0 7.0 2188.0 2012 1.0 51.616884 -0.244740 2 2 30.0 0 0.974928 -0.222521 1.000000 6.123234e-17 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0
4 2.0 6.0 1968.0 2012 1.0 54.952193 -1.554247 1 2 30.0 0 0.974928 -0.222521 -0.500000 8.660254e-01 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

4.3.3. Feature scaling:¶

In questa fase, l'obiettivo è quello di rendere i valori degli attributi sulla stessa scala, in modo tale da poter avere buone performance anche per algoritmi di machine learning che non siano alberi decisionali:

In [ ]:
train_set.describe()
Out[ ]:
Age_Band_of_Driver Age_of_Vehicle Engine_Capacity_.CC. Year Did_Police_Officer_Attend_Scene_of_Accident Latitude Longitude Number_of_Casualties Number_of_Vehicles Speed_limit Accident_Severity Day_of_Week_sin Day_of_Week_cos Time_Interval_sin Time_Interval_cos Journey_Purpose_of_Driver_Commuting to/from work Journey_Purpose_of_Driver_Journey as part of work Journey_Purpose_of_Driver_Not known Journey_Purpose_of_Driver_Other Journey_Purpose_of_Driver_Other/Not known (2005-10) Journey_Purpose_of_Driver_Pupil riding to/from school Journey_Purpose_of_Driver_Taking pupil to/from school Junction_Location_Approaching junction or waiting/parked at junction approach Junction_Location_Cleared junction or waiting/parked at junction exit Junction_Location_Entering from slip road Junction_Location_Entering main road Junction_Location_Entering roundabout Junction_Location_Leaving main road Junction_Location_Leaving roundabout Junction_Location_Mid Junction - on roundabout or on main road Junction_Location_Not at or within 20 metres of junction make_ALFA ROMEO make_APRILIA make_AUDI make_BMW make_CHEVROLET make_CHRYSLER make_CITROEN make_DAEWOO make_DAIHATSU make_DUCATI make_FIAT make_FORD make_HARLEY-DAVIDSON make_HONDA make_HYUNDAI make_IVECO make_JAGUAR make_KAWASAKI make_KIA make_KTM make_LAND ROVER make_LEXUS make_LONDON TAXIS INT make_MAZDA make_MERCEDES make_MG make_MINI make_MITSUBISHI make_NISSAN make_OTHERS make_PEUGEOT make_PIAGGIO make_RENAULT make_ROVER make_SAAB make_SEAT make_SKODA make_SMART make_SUBARU make_SUZUKI make_TOYOTA make_TRIUMPH make_VAUXHALL make_VOLKSWAGEN make_VOLVO make_YAMAHA Vehicle_Manoeuvre_Changing lane to left Vehicle_Manoeuvre_Changing lane to right Vehicle_Manoeuvre_Going ahead left-hand bend Vehicle_Manoeuvre_Going ahead other Vehicle_Manoeuvre_Going ahead right-hand bend Vehicle_Manoeuvre_Moving off Vehicle_Manoeuvre_Overtaking - nearside Vehicle_Manoeuvre_Overtaking moving vehicle - offside Vehicle_Manoeuvre_Overtaking static vehicle - offside Vehicle_Manoeuvre_Parked Vehicle_Manoeuvre_Reversing Vehicle_Manoeuvre_Slowing or stopping Vehicle_Manoeuvre_Turning left Vehicle_Manoeuvre_Turning right Vehicle_Manoeuvre_U-turn Vehicle_Manoeuvre_Waiting to go - held up Vehicle_Manoeuvre_Waiting to turn left Vehicle_Manoeuvre_Waiting to turn right Vehicle_Type_Agricultural vehicle Vehicle_Type_Bus or coach (17 or more pass seats) Vehicle_Type_Car Vehicle_Type_Electric motorcycle Vehicle_Type_Goods 7.5 tonnes mgw and over Vehicle_Type_Goods over 3.5t. and under 7.5t Vehicle_Type_Goods vehicle - unknown weight Vehicle_Type_Minibus (8 - 16 passenger seats) Vehicle_Type_Motorcycle - unknown cc Vehicle_Type_Motorcycle 125cc and under Vehicle_Type_Motorcycle 50cc and under Vehicle_Type_Motorcycle over 125cc and up to 500cc Vehicle_Type_Motorcycle over 500cc Vehicle_Type_Other vehicle Vehicle_Type_Taxi/Private hire car Vehicle_Type_Van / Goods 3.5 tonnes mgw or under X1st_Point_of_Impact_Back X1st_Point_of_Impact_Did not impact X1st_Point_of_Impact_Front X1st_Point_of_Impact_Nearside X1st_Point_of_Impact_Offside 1st_Road_Class_A 1st_Road_Class_A(M) 1st_Road_Class_B 1st_Road_Class_C 1st_Road_Class_Motorway 1st_Road_Class_Unclassified Junction_Detail_Crossroads Junction_Detail_Mini-roundabout Junction_Detail_More than 4 arms (not roundabout) Junction_Detail_Not at junction or within 20 metres Junction_Detail_Other junction Junction_Detail_Private drive or entrance Junction_Detail_Roundabout Junction_Detail_Slip road Junction_Detail_T or staggered junction Local_Authority_(District)_0 Local_Authority_(District)_1 Local_Authority_(District)_10 Local_Authority_(District)_11 Local_Authority_(District)_12 Local_Authority_(District)_13 Local_Authority_(District)_14 Local_Authority_(District)_15 Local_Authority_(District)_16 Local_Authority_(District)_17 Local_Authority_(District)_18 Local_Authority_(District)_19 Local_Authority_(District)_2 Local_Authority_(District)_3 Local_Authority_(District)_4 Local_Authority_(District)_5 Local_Authority_(District)_6 Local_Authority_(District)_7 Local_Authority_(District)_8 Local_Authority_(District)_9 Local_Authority_(Highway)_0 Local_Authority_(Highway)_1 Local_Authority_(Highway)_10 Local_Authority_(Highway)_11 Local_Authority_(Highway)_12 Local_Authority_(Highway)_13 Local_Authority_(Highway)_14 Local_Authority_(Highway)_15 Local_Authority_(Highway)_16 Local_Authority_(Highway)_17 Local_Authority_(Highway)_18 Local_Authority_(Highway)_19 Local_Authority_(Highway)_2 Local_Authority_(Highway)_3 Local_Authority_(Highway)_4 Local_Authority_(Highway)_5 Local_Authority_(Highway)_6 Local_Authority_(Highway)_7 Local_Authority_(Highway)_8 Local_Authority_(Highway)_9 Road_Type_Dual carriageway Road_Type_One way street Road_Type_Roundabout Road_Type_Single carriageway Road_Type_Slip road Road_Type_Unknown Weather_Conditions_Fine + high winds Weather_Conditions_Fine no high winds Weather_Conditions_Fog or mist Weather_Conditions_Other Weather_Conditions_Raining + high winds Weather_Conditions_Raining no high winds Weather_Conditions_Snowing + high winds Weather_Conditions_Snowing no high winds Weather_Conditions_Unknown
count 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 3.325640e+05 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.00000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.00000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000 332564.000000
mean 3.966491 7.179611 1564.904178 2011.368531 1.122097 52.611099 -1.423594 1.374995 1.921173 40.226362 0.499994 0.030536 -0.041387 -0.245658 -3.320294e-01 0.116212 0.132074 0.423518 0.020586 0.294689 0.001588 0.011333 0.214052 0.050547 0.00335 0.056642 0.030021 0.032511 0.014157 0.227222 0.371498 0.002733 0.004270 0.026548 0.036480 0.002715 0.001879 0.040407 0.003118 0.002090 0.002294 0.029137 0.119132 0.002881 0.061669 0.012070 0.002240 0.004062 0.014518 0.010392 0.001509 0.012220 0.002096 0.005184 0.012163 0.031955 0.003936 0.008934 0.008149 0.035323 0.020113 0.066240 0.004192 0.055839 0.017200 0.004601 0.012133 0.016024 0.001891 0.002769 0.032640 0.044265 0.006486 0.121968 0.061949 0.012085 0.019500 0.006970 0.007899 0.047110 0.462302 0.050926 0.040567 0.004658 0.026064 0.012677 0.022501 0.011117 0.056939 0.033200 0.132432 0.010963 0.050249 0.005818 0.017609 0.000108 0.000493 0.789445 0.000021 0.00028 0.001110 0.000123 0.001470 0.000216 0.026395 0.007980 0.014833 0.075910 0.001314 0.024885 0.055415 0.141332 0.036748 0.520904 0.138437 0.162579 0.479724 0.002610 0.137159 0.083340 0.032872 0.264295 0.106635 0.011538 0.011459 0.371147 0.025613 0.045393 0.088596 0.013564 0.326055 0.031504 0.081172 0.045324 0.008528 0.110018 0.203874 0.033849 0.026879 0.039625 0.035010 0.000177 0.000168 0.060343 0.019620 0.020198 0.066763 0.012136 0.046481 0.049422 0.108908 0.039232 0.082862 0.033627 0.115373 0.024885 0.024272 0.000177 0.088266 0.093029 0.000168 0.004207 0.051822 0.041908 0.033497 0.011772 0.219609 0.007929 0.082162 0.003196 0.042007 0.142718 0.014451 0.070374 0.758654 0.009896 0.003906 0.013321 0.817548 0.005220 0.018273 0.012891 0.112802 0.001055 0.005894 0.012996
std 1.791501 4.419928 569.858981 3.258793 0.330051 1.483040 1.380300 0.660196 0.480276 14.398632 0.500001 0.700107 0.712186 0.699621 5.830447e-01 0.320480 0.338572 0.494117 0.141992 0.455904 0.039814 0.105852 0.410164 0.219070 0.05778 0.231157 0.170646 0.177353 0.118137 0.419038 0.483206 0.052210 0.065205 0.160759 0.187482 0.052038 0.043311 0.196913 0.055754 0.045667 0.047844 0.168191 0.323944 0.053594 0.240554 0.109198 0.047277 0.063607 0.119611 0.101410 0.038823 0.109868 0.045732 0.071813 0.109614 0.175880 0.062615 0.094095 0.089902 0.184594 0.140389 0.248701 0.064607 0.229611 0.130015 0.067672 0.109480 0.125568 0.043449 0.052552 0.177694 0.205684 0.080274 0.327249 0.241063 0.109265 0.138274 0.083196 0.088526 0.211874 0.498578 0.219846 0.197284 0.068089 0.159326 0.111878 0.148306 0.104848 0.231727 0.179158 0.338960 0.104130 0.218459 0.076057 0.131524 0.010404 0.022201 0.407703 0.004588 0.01672 0.033292 0.011103 0.038318 0.014712 0.160307 0.088976 0.120885 0.264855 0.036226 0.155776 0.228789 0.348364 0.188142 0.499564 0.345358 0.368982 0.499589 0.051022 0.344015 0.276396 0.178301 0.440958 0.308649 0.106792 0.106434 0.483112 0.157978 0.208164 0.284161 0.115673 0.468768 0.174675 0.273100 0.208013 0.091951 0.312913 0.402877 0.180841 0.161730 0.195078 0.183805 0.013318 0.012975 0.238122 0.138692 0.140676 0.249612 0.109493 0.210525 0.216748 0.311525 0.194146 0.275674 0.180266 0.319472 0.155776 0.153893 0.013318 0.283681 0.290473 0.012975 0.064723 0.221667 0.200379 0.179931 0.107859 0.413982 0.088693 0.274611 0.056446 0.200605 0.349786 0.119342 0.255777 0.427900 0.098985 0.062376 0.114644 0.386217 0.072061 0.133938 0.112803 0.316352 0.032470 0.076543 0.113257
min 0.000000 1.000000 10.000000 2005.000000 1.000000 49.915618 -7.516225 1.000000 1.000000 20.000000 0.000000 -0.974928 -0.900969 -1.000000 -1.000000e+00 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.00000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.00000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
25% 3.000000 3.000000 1242.000000 2009.000000 1.000000 51.481299 -2.322458 1.000000 2.000000 30.000000 0.000000 -0.781831 -0.900969 -0.866025 -8.660254e-01 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.00000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.00000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
50% 4.000000 7.000000 1596.000000 2012.000000 1.000000 52.358795 -1.404380 1.000000 2.000000 30.000000 0.000000 0.000000 -0.222521 -0.500000 -5.000000e-01 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.00000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.00000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
75% 5.000000 10.000000 1968.000000 2014.000000 1.000000 53.499203 -0.261969 2.000000 2.000000 60.000000 1.000000 0.781831 0.623490 0.500000 -1.836970e-16 0.000000 0.000000 1.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.00000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.00000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
max 8.000000 20.000000 3700.000000 2016.000000 3.000000 60.662043 1.758661 6.000000 6.000000 70.000000 1.000000 0.974928 1.000000 1.000000 1.000000e+00 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.00000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.00000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000

Non resta che portare tutti gli attributi sulla stessa scala, basta vedere ad esempio i valori massimi o medi tra Age_Band_of_Driver e EngineCapacity.CC., in cui è presente una differenza sostanziale.

In [ ]:
print("Prima -->", train_set.shape)
train_set_X = train_set.loc[:, train_set.columns != 'Accident_Severity']
scaler = StandardScaler()
scaled_features = scaler.fit_transform(train_set_X)
train_set_X, train_set_y = pd.DataFrame(scaled_features, index=train_set_X.index, columns=train_set_X.columns), train_set['Accident_Severity']
print("Dopo X_train -->", train_set_X.shape)
print("Dopo y_train -->", train_set_y.shape)
Prima --> (332564, 186)
Dopo X_train --> (332564, 185)
Dopo y_train --> (332564,)
In [ ]:
train_set_X.describe()
Out[ ]:
Age_Band_of_Driver Age_of_Vehicle Engine_Capacity_.CC. Year Did_Police_Officer_Attend_Scene_of_Accident Latitude Longitude Number_of_Casualties Number_of_Vehicles Speed_limit Day_of_Week_sin Day_of_Week_cos Time_Interval_sin Time_Interval_cos Journey_Purpose_of_Driver_Commuting to/from work Journey_Purpose_of_Driver_Journey as part of work Journey_Purpose_of_Driver_Not known Journey_Purpose_of_Driver_Other Journey_Purpose_of_Driver_Other/Not known (2005-10) Journey_Purpose_of_Driver_Pupil riding to/from school Journey_Purpose_of_Driver_Taking pupil to/from school Junction_Location_Approaching junction or waiting/parked at junction approach Junction_Location_Cleared junction or waiting/parked at junction exit Junction_Location_Entering from slip road Junction_Location_Entering main road Junction_Location_Entering roundabout Junction_Location_Leaving main road Junction_Location_Leaving roundabout Junction_Location_Mid Junction - on roundabout or on main road Junction_Location_Not at or within 20 metres of junction make_ALFA ROMEO make_APRILIA make_AUDI make_BMW make_CHEVROLET make_CHRYSLER make_CITROEN make_DAEWOO make_DAIHATSU make_DUCATI make_FIAT make_FORD make_HARLEY-DAVIDSON make_HONDA make_HYUNDAI make_IVECO make_JAGUAR make_KAWASAKI make_KIA make_KTM make_LAND ROVER make_LEXUS make_LONDON TAXIS INT make_MAZDA make_MERCEDES make_MG make_MINI make_MITSUBISHI make_NISSAN make_OTHERS make_PEUGEOT make_PIAGGIO make_RENAULT make_ROVER make_SAAB make_SEAT make_SKODA make_SMART make_SUBARU make_SUZUKI make_TOYOTA make_TRIUMPH make_VAUXHALL make_VOLKSWAGEN make_VOLVO make_YAMAHA Vehicle_Manoeuvre_Changing lane to left Vehicle_Manoeuvre_Changing lane to right Vehicle_Manoeuvre_Going ahead left-hand bend Vehicle_Manoeuvre_Going ahead other Vehicle_Manoeuvre_Going ahead right-hand bend Vehicle_Manoeuvre_Moving off Vehicle_Manoeuvre_Overtaking - nearside Vehicle_Manoeuvre_Overtaking moving vehicle - offside Vehicle_Manoeuvre_Overtaking static vehicle - offside Vehicle_Manoeuvre_Parked Vehicle_Manoeuvre_Reversing Vehicle_Manoeuvre_Slowing or stopping Vehicle_Manoeuvre_Turning left Vehicle_Manoeuvre_Turning right Vehicle_Manoeuvre_U-turn Vehicle_Manoeuvre_Waiting to go - held up Vehicle_Manoeuvre_Waiting to turn left Vehicle_Manoeuvre_Waiting to turn right Vehicle_Type_Agricultural vehicle Vehicle_Type_Bus or coach (17 or more pass seats) Vehicle_Type_Car Vehicle_Type_Electric motorcycle Vehicle_Type_Goods 7.5 tonnes mgw and over Vehicle_Type_Goods over 3.5t. and under 7.5t Vehicle_Type_Goods vehicle - unknown weight Vehicle_Type_Minibus (8 - 16 passenger seats) Vehicle_Type_Motorcycle - unknown cc Vehicle_Type_Motorcycle 125cc and under Vehicle_Type_Motorcycle 50cc and under Vehicle_Type_Motorcycle over 125cc and up to 500cc Vehicle_Type_Motorcycle over 500cc Vehicle_Type_Other vehicle Vehicle_Type_Taxi/Private hire car Vehicle_Type_Van / Goods 3.5 tonnes mgw or under X1st_Point_of_Impact_Back X1st_Point_of_Impact_Did not impact X1st_Point_of_Impact_Front X1st_Point_of_Impact_Nearside X1st_Point_of_Impact_Offside 1st_Road_Class_A 1st_Road_Class_A(M) 1st_Road_Class_B 1st_Road_Class_C 1st_Road_Class_Motorway 1st_Road_Class_Unclassified Junction_Detail_Crossroads Junction_Detail_Mini-roundabout Junction_Detail_More than 4 arms (not roundabout) Junction_Detail_Not at junction or within 20 metres Junction_Detail_Other junction Junction_Detail_Private drive or entrance Junction_Detail_Roundabout Junction_Detail_Slip road Junction_Detail_T or staggered junction Local_Authority_(District)_0 Local_Authority_(District)_1 Local_Authority_(District)_10 Local_Authority_(District)_11 Local_Authority_(District)_12 Local_Authority_(District)_13 Local_Authority_(District)_14 Local_Authority_(District)_15 Local_Authority_(District)_16 Local_Authority_(District)_17 Local_Authority_(District)_18 Local_Authority_(District)_19 Local_Authority_(District)_2 Local_Authority_(District)_3 Local_Authority_(District)_4 Local_Authority_(District)_5 Local_Authority_(District)_6 Local_Authority_(District)_7 Local_Authority_(District)_8 Local_Authority_(District)_9 Local_Authority_(Highway)_0 Local_Authority_(Highway)_1 Local_Authority_(Highway)_10 Local_Authority_(Highway)_11 Local_Authority_(Highway)_12 Local_Authority_(Highway)_13 Local_Authority_(Highway)_14 Local_Authority_(Highway)_15 Local_Authority_(Highway)_16 Local_Authority_(Highway)_17 Local_Authority_(Highway)_18 Local_Authority_(Highway)_19 Local_Authority_(Highway)_2 Local_Authority_(Highway)_3 Local_Authority_(Highway)_4 Local_Authority_(Highway)_5 Local_Authority_(Highway)_6 Local_Authority_(Highway)_7 Local_Authority_(Highway)_8 Local_Authority_(Highway)_9 Road_Type_Dual carriageway Road_Type_One way street Road_Type_Roundabout Road_Type_Single carriageway Road_Type_Slip road Road_Type_Unknown Weather_Conditions_Fine + high winds Weather_Conditions_Fine no high winds Weather_Conditions_Fog or mist Weather_Conditions_Other Weather_Conditions_Raining + high winds Weather_Conditions_Raining no high winds Weather_Conditions_Snowing + high winds Weather_Conditions_Snowing no high winds Weather_Conditions_Unknown
count 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05 3.325640e+05
mean 4.623316e-15 -5.664097e-15 1.435521e-14 1.518574e-14 5.199369e-13 2.669815e-15 6.154192e-17 -1.924623e-13 -4.517127e-13 5.172120e-14 3.546034e-14 -2.092001e-14 -2.292259e-14 -3.240941e-14 -1.176168e-14 -8.860316e-15 -5.428479e-15 2.480550e-14 1.367618e-15 -6.335245e-16 -1.341869e-15 -1.127924e-13 -9.902704e-16 -1.725220e-14 -8.641879e-14 2.555796e-13 -8.238199e-15 -4.229166e-14 -1.853246e-13 2.352970e-14 -4.650737e-15 -1.730447e-14 1.538848e-14 -2.137653e-14 -8.054145e-15 3.279765e-15 -6.527677e-14 -1.191398e-14 3.051109e-15 3.442164e-15 3.142583e-14 6.189213e-14 1.182808e-13 1.215595e-13 1.494999e-14 6.369352e-15 5.406998e-17 -2.280113e-13 7.849542e-15 -4.554964e-14 8.023766e-15 -1.187515e-14 -1.330892e-14 -6.683220e-15 4.892247e-14 -1.935676e-15 -3.214301e-14 -1.482408e-15 1.381629e-14 -1.264191e-13 -4.966287e-14 1.979066e-13 3.355857e-14 -2.856724e-14 1.703448e-14 -1.233286e-14 -1.815475e-14 -3.177032e-14 1.621087e-14 2.327194e-13 7.158854e-14 -9.896966e-14 3.634902e-14 7.167841e-14 2.683089e-16 -1.420919e-13 4.874309e-15 -2.334289e-14 1.355827e-13 -1.346229e-13 -2.957528e-13 -2.806658e-14 -1.451219e-14 1.455842e-13 -1.143778e-13 2.619961e-15 -2.233797e-14 -2.156327e-13 -9.224180e-14 6.185753e-14 -4.212766e-15 1.159516e-13 -1.081451e-13 6.009825e-14 2.279590e-14 -2.600342e-15 -1.559218e-13 7.229099e-15 -6.328297e-15 5.678562e-15 -2.805521e-15 -2.848381e-15 -1.747096e-14 5.120421e-13 -6.473366e-14 -2.813008e-13 -3.062970e-13 1.146089e-14 -1.779863e-14 7.188442e-15 5.935955e-13 -8.565452e-14 -2.381048e-13 -4.067094e-14 -5.063360e-15 2.091510e-14 -2.782875e-15 -6.720181e-15 -5.724628e-15 -4.443515e-14 5.969065e-14 -7.047890e-14 1.198895e-13 -1.309216e-14 3.094762e-13 2.167783e-14 -3.023852e-15 -1.420714e-13 -1.744732e-14 -5.492172e-15 -3.267520e-14 4.239440e-14 3.207654e-14 -5.285016e-14 -1.625067e-16 9.476737e-15 -2.635370e-14 -5.692766e-14 1.565589e-14 7.298859e-14 -1.512149e-14 3.806777e-15 -1.249253e-14 -4.679024e-14 -1.637030e-14 -6.762462e-14 -4.263053e-14 -3.566005e-16 -1.482717e-14 2.863599e-14 -2.755234e-14 1.070881e-13 2.254020e-14 6.046995e-15 -2.596575e-14 -3.556813e-15 -1.512149e-14 -2.875652e-14 -3.972978e-14 3.806777e-15 -6.982550e-14 8.155019e-15 5.252823e-15 -2.544325e-14 -1.162649e-14 5.792304e-14 -1.103862e-13 3.671653e-15 2.010435e-14 -1.200381e-14 5.689792e-14 -6.244151e-15 3.517882e-14 8.531897e-14 -5.565734e-14 -5.307353e-14 1.749337e-14 6.092677e-14 -1.903435e-14 -6.428460e-15 -7.881757e-15 -5.723709e-14 -6.332313e-15 -4.825001e-14 5.937877e-14
std 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00 1.000002e+00
min -2.214064e+00 -1.398127e+00 -2.728581e+00 -1.954264e+00 -3.699339e-01 -1.817541e+00 -4.413996e+00 -5.680075e-01 -1.918012e+00 -1.404744e+00 -1.436160e+00 -1.206965e+00 -1.078216e+00 -1.145661e+00 -3.626201e-01 -3.900919e-01 -8.571237e-01 -1.449765e-01 -6.463858e-01 -3.987717e-02 -1.070657e-01 -5.218705e-01 -2.307328e-01 -5.797404e-02 -2.450360e-01 -1.759274e-01 -1.833126e-01 -1.198331e-01 -5.422485e-01 -7.688211e-01 -5.235265e-02 -6.548408e-02 -1.651433e-01 -1.945800e-01 -5.217911e-02 -4.339212e-02 -2.052042e-01 -5.592808e-02 -4.576243e-02 -4.795384e-02 -1.732389e-01 -3.677551e-01 -5.374915e-02 -2.563638e-01 -1.105320e-01 -4.738354e-02 -6.386663e-02 -1.213729e-01 -1.024749e-01 -3.888143e-02 -1.112267e-01 -4.582836e-02 -7.218710e-02 -1.109632e-01 -1.816853e-01 -6.286206e-02 -9.494287e-02 -9.064081e-02 -1.913528e-01 -1.432699e-01 -2.663435e-01 -6.487926e-02 -2.431898e-01 -1.322902e-01 -6.798442e-02 -1.108243e-01 -1.276124e-01 -4.353102e-02 -5.269803e-02 -1.836891e-01 -2.152099e-01 -8.079797e-02 -3.727063e-01 -2.569827e-01 -1.106016e-01 -1.410242e-01 -8.377953e-02 -8.923075e-02 -2.223483e-01 -9.272432e-01 -2.316422e-01 -2.056255e-01 -6.840721e-02 -1.635900e-01 -1.133139e-01 -1.517197e-01 -1.060265e-01 -2.457179e-01 -1.853097e-01 -3.907006e-01 -1.052845e-01 -2.300164e-01 -7.650151e-02 -1.338815e-01 -1.040488e-02 -2.221219e-02 -1.936325e+00 -4.587921e-03 -1.672494e-02 -3.332857e-02 -1.110404e-02 -3.837394e-02 -1.471552e-02 -1.646527e-01 -8.969180e-02 -1.227052e-01 -2.866111e-01 -3.627343e-02 -1.597514e-01 -2.422104e-01 -4.057028e-01 -1.953196e-01 -1.042720e+00 -4.008498e-01 -4.406164e-01 -9.602383e-01 -5.115520e-02 -3.986998e-01 -3.015252e-01 -1.843615e-01 -5.993667e-01 -3.454902e-01 -1.080384e-01 -1.076675e-01 -7.682420e-01 -1.621308e-01 -2.180625e-01 -3.117833e-01 -1.172639e-01 -6.955570e-01 -1.803565e-01 -2.972262e-01 -2.178884e-01 -9.274173e-02 -3.515937e-01 -5.060453e-01 -1.871765e-01 -1.661971e-01 -2.031266e-01 -1.904730e-01 -1.332070e-02 -1.297756e-02 -2.534135e-01 -1.414671e-01 -1.435757e-01 -2.674683e-01 -1.108382e-01 -2.207875e-01 -2.280167e-01 -3.495984e-01 -2.020730e-01 -3.005807e-01 -1.865388e-01 -3.611375e-01 -1.597514e-01 -1.577207e-01 -1.332070e-02 -3.111443e-01 -3.202667e-01 -1.297756e-02 -6.499600e-02 -2.337816e-01 -2.091430e-01 -1.861673e-01 -1.091440e-01 -5.304797e-01 -8.940177e-02 -2.991930e-01 -5.662709e-02 -2.094013e-01 -4.080170e-01 -1.210919e-01 -2.751398e-01 -1.772972e+00 -9.997373e-02 -6.262054e-02 -1.161920e-01 -2.116811e+00 -7.243921e-02 -1.364306e-01 -1.142764e-01 -3.565733e-01 -3.250463e-02 -7.699703e-02 -1.147480e-01
25% -5.394874e-01 -9.456300e-01 -5.666396e-01 -7.268134e-01 -3.699339e-01 -7.618153e-01 -6.512098e-01 -5.680075e-01 1.641288e-01 -7.102326e-01 -1.160349e+00 -1.206965e+00 -8.867203e-01 -9.158763e-01 -3.626201e-01 -3.900919e-01 -8.571237e-01 -1.449765e-01 -6.463858e-01 -3.987717e-02 -1.070657e-01 -5.218705e-01 -2.307328e-01 -5.797404e-02 -2.450360e-01 -1.759274e-01 -1.833126e-01 -1.198331e-01 -5.422485e-01 -7.688211e-01 -5.235265e-02 -6.548408e-02 -1.651433e-01 -1.945800e-01 -5.217911e-02 -4.339212e-02 -2.052042e-01 -5.592808e-02 -4.576243e-02 -4.795384e-02 -1.732389e-01 -3.677551e-01 -5.374915e-02 -2.563638e-01 -1.105320e-01 -4.738354e-02 -6.386663e-02 -1.213729e-01 -1.024749e-01 -3.888143e-02 -1.112267e-01 -4.582836e-02 -7.218710e-02 -1.109632e-01 -1.816853e-01 -6.286206e-02 -9.494287e-02 -9.064081e-02 -1.913528e-01 -1.432699e-01 -2.663435e-01 -6.487926e-02 -2.431898e-01 -1.322902e-01 -6.798442e-02 -1.108243e-01 -1.276124e-01 -4.353102e-02 -5.269803e-02 -1.836891e-01 -2.152099e-01 -8.079797e-02 -3.727063e-01 -2.569827e-01 -1.106016e-01 -1.410242e-01 -8.377953e-02 -8.923075e-02 -2.223483e-01 -9.272432e-01 -2.316422e-01 -2.056255e-01 -6.840721e-02 -1.635900e-01 -1.133139e-01 -1.517197e-01 -1.060265e-01 -2.457179e-01 -1.853097e-01 -3.907006e-01 -1.052845e-01 -2.300164e-01 -7.650151e-02 -1.338815e-01 -1.040488e-02 -2.221219e-02 5.164423e-01 -4.587921e-03 -1.672494e-02 -3.332857e-02 -1.110404e-02 -3.837394e-02 -1.471552e-02 -1.646527e-01 -8.969180e-02 -1.227052e-01 -2.866111e-01 -3.627343e-02 -1.597514e-01 -2.422104e-01 -4.057028e-01 -1.953196e-01 -1.042720e+00 -4.008498e-01 -4.406164e-01 -9.602383e-01 -5.115520e-02 -3.986998e-01 -3.015252e-01 -1.843615e-01 -5.993667e-01 -3.454902e-01 -1.080384e-01 -1.076675e-01 -7.682420e-01 -1.621308e-01 -2.180625e-01 -3.117833e-01 -1.172639e-01 -6.955570e-01 -1.803565e-01 -2.972262e-01 -2.178884e-01 -9.274173e-02 -3.515937e-01 -5.060453e-01 -1.871765e-01 -1.661971e-01 -2.031266e-01 -1.904730e-01 -1.332070e-02 -1.297756e-02 -2.534135e-01 -1.414671e-01 -1.435757e-01 -2.674683e-01 -1.108382e-01 -2.207875e-01 -2.280167e-01 -3.495984e-01 -2.020730e-01 -3.005807e-01 -1.865388e-01 -3.611375e-01 -1.597514e-01 -1.577207e-01 -1.332070e-02 -3.111443e-01 -3.202667e-01 -1.297756e-02 -6.499600e-02 -2.337816e-01 -2.091430e-01 -1.861673e-01 -1.091440e-01 -5.304797e-01 -8.940177e-02 -2.991930e-01 -5.662709e-02 -2.094013e-01 -4.080170e-01 -1.210919e-01 -2.751398e-01 5.640248e-01 -9.997373e-02 -6.262054e-02 -1.161920e-01 4.724086e-01 -7.243921e-02 -1.364306e-01 -1.142764e-01 -3.565733e-01 -3.250463e-02 -7.699703e-02 -1.147480e-01
50% 1.870465e-02 -4.063659e-02 5.456766e-02 1.937743e-01 -3.699339e-01 -1.701269e-01 1.392038e-02 -5.680075e-01 1.641288e-01 -7.102326e-01 -4.361580e-02 -2.543356e-01 -3.635431e-01 -2.880926e-01 -3.626201e-01 -3.900919e-01 -8.571237e-01 -1.449765e-01 -6.463858e-01 -3.987717e-02 -1.070657e-01 -5.218705e-01 -2.307328e-01 -5.797404e-02 -2.450360e-01 -1.759274e-01 -1.833126e-01 -1.198331e-01 -5.422485e-01 -7.688211e-01 -5.235265e-02 -6.548408e-02 -1.651433e-01 -1.945800e-01 -5.217911e-02 -4.339212e-02 -2.052042e-01 -5.592808e-02 -4.576243e-02 -4.795384e-02 -1.732389e-01 -3.677551e-01 -5.374915e-02 -2.563638e-01 -1.105320e-01 -4.738354e-02 -6.386663e-02 -1.213729e-01 -1.024749e-01 -3.888143e-02 -1.112267e-01 -4.582836e-02 -7.218710e-02 -1.109632e-01 -1.816853e-01 -6.286206e-02 -9.494287e-02 -9.064081e-02 -1.913528e-01 -1.432699e-01 -2.663435e-01 -6.487926e-02 -2.431898e-01 -1.322902e-01 -6.798442e-02 -1.108243e-01 -1.276124e-01 -4.353102e-02 -5.269803e-02 -1.836891e-01 -2.152099e-01 -8.079797e-02 -3.727063e-01 -2.569827e-01 -1.106016e-01 -1.410242e-01 -8.377953e-02 -8.923075e-02 -2.223483e-01 -9.272432e-01 -2.316422e-01 -2.056255e-01 -6.840721e-02 -1.635900e-01 -1.133139e-01 -1.517197e-01 -1.060265e-01 -2.457179e-01 -1.853097e-01 -3.907006e-01 -1.052845e-01 -2.300164e-01 -7.650151e-02 -1.338815e-01 -1.040488e-02 -2.221219e-02 5.164423e-01 -4.587921e-03 -1.672494e-02 -3.332857e-02 -1.110404e-02 -3.837394e-02 -1.471552e-02 -1.646527e-01 -8.969180e-02 -1.227052e-01 -2.866111e-01 -3.627343e-02 -1.597514e-01 -2.422104e-01 -4.057028e-01 -1.953196e-01 9.590300e-01 -4.008498e-01 -4.406164e-01 -9.602383e-01 -5.115520e-02 -3.986998e-01 -3.015252e-01 -1.843615e-01 -5.993667e-01 -3.454902e-01 -1.080384e-01 -1.076675e-01 -7.682420e-01 -1.621308e-01 -2.180625e-01 -3.117833e-01 -1.172639e-01 -6.955570e-01 -1.803565e-01 -2.972262e-01 -2.178884e-01 -9.274173e-02 -3.515937e-01 -5.060453e-01 -1.871765e-01 -1.661971e-01 -2.031266e-01 -1.904730e-01 -1.332070e-02 -1.297756e-02 -2.534135e-01 -1.414671e-01 -1.435757e-01 -2.674683e-01 -1.108382e-01 -2.207875e-01 -2.280167e-01 -3.495984e-01 -2.020730e-01 -3.005807e-01 -1.865388e-01 -3.611375e-01 -1.597514e-01 -1.577207e-01 -1.332070e-02 -3.111443e-01 -3.202667e-01 -1.297756e-02 -6.499600e-02 -2.337816e-01 -2.091430e-01 -1.861673e-01 -1.091440e-01 -5.304797e-01 -8.940177e-02 -2.991930e-01 -5.662709e-02 -2.094013e-01 -4.080170e-01 -1.210919e-01 -2.751398e-01 5.640248e-01 -9.997373e-02 -6.262054e-02 -1.161920e-01 4.724086e-01 -7.243921e-02 -1.364306e-01 -1.142764e-01 -3.565733e-01 -3.250463e-02 -7.699703e-02 -1.147480e-01
75% 5.768967e-01 6.381084e-01 7.073617e-01 8.074995e-01 -3.699339e-01 5.988410e-01 8.415754e-01 9.466974e-01 1.641288e-01 1.373302e+00 1.073118e+00 9.335739e-01 1.065804e+00 5.694758e-01 -3.626201e-01 -3.900919e-01 1.166693e+00 -1.449765e-01 1.547064e+00 -3.987717e-02 -1.070657e-01 -5.218705e-01 -2.307328e-01 -5.797404e-02 -2.450360e-01 -1.759274e-01 -1.833126e-01 -1.198331e-01 -5.422485e-01 1.300693e+00 -5.235265e-02 -6.548408e-02 -1.651433e-01 -1.945800e-01 -5.217911e-02 -4.339212e-02 -2.052042e-01 -5.592808e-02 -4.576243e-02 -4.795384e-02 -1.732389e-01 -3.677551e-01 -5.374915e-02 -2.563638e-01 -1.105320e-01 -4.738354e-02 -6.386663e-02 -1.213729e-01 -1.024749e-01 -3.888143e-02 -1.112267e-01 -4.582836e-02 -7.218710e-02 -1.109632e-01 -1.816853e-01 -6.286206e-02 -9.494287e-02 -9.064081e-02 -1.913528e-01 -1.432699e-01 -2.663435e-01 -6.487926e-02 -2.431898e-01 -1.322902e-01 -6.798442e-02 -1.108243e-01 -1.276124e-01 -4.353102e-02 -5.269803e-02 -1.836891e-01 -2.152099e-01 -8.079797e-02 -3.727063e-01 -2.569827e-01 -1.106016e-01 -1.410242e-01 -8.377953e-02 -8.923075e-02 -2.223483e-01 1.078466e+00 -2.316422e-01 -2.056255e-01 -6.840721e-02 -1.635900e-01 -1.133139e-01 -1.517197e-01 -1.060265e-01 -2.457179e-01 -1.853097e-01 -3.907006e-01 -1.052845e-01 -2.300164e-01 -7.650151e-02 -1.338815e-01 -1.040488e-02 -2.221219e-02 5.164423e-01 -4.587921e-03 -1.672494e-02 -3.332857e-02 -1.110404e-02 -3.837394e-02 -1.471552e-02 -1.646527e-01 -8.969180e-02 -1.227052e-01 -2.866111e-01 -3.627343e-02 -1.597514e-01 -2.422104e-01 -4.057028e-01 -1.953196e-01 9.590300e-01 -4.008498e-01 -4.406164e-01 1.041408e+00 -5.115520e-02 -3.986998e-01 -3.015252e-01 -1.843615e-01 1.668428e+00 -3.454902e-01 -1.080384e-01 -1.076675e-01 1.301673e+00 -1.621308e-01 -2.180625e-01 -3.117833e-01 -1.172639e-01 1.437697e+00 -1.803565e-01 -2.972262e-01 -2.178884e-01 -9.274173e-02 -3.515937e-01 -5.060453e-01 -1.871765e-01 -1.661971e-01 -2.031266e-01 -1.904730e-01 -1.332070e-02 -1.297756e-02 -2.534135e-01 -1.414671e-01 -1.435757e-01 -2.674683e-01 -1.108382e-01 -2.207875e-01 -2.280167e-01 -3.495984e-01 -2.020730e-01 -3.005807e-01 -1.865388e-01 -3.611375e-01 -1.597514e-01 -1.577207e-01 -1.332070e-02 -3.111443e-01 -3.202667e-01 -1.297756e-02 -6.499600e-02 -2.337816e-01 -2.091430e-01 -1.861673e-01 -1.091440e-01 -5.304797e-01 -8.940177e-02 -2.991930e-01 -5.662709e-02 -2.094013e-01 -4.080170e-01 -1.210919e-01 -2.751398e-01 5.640248e-01 -9.997373e-02 -6.262054e-02 -1.161920e-01 4.724086e-01 -7.243921e-02 -1.364306e-01 -1.142764e-01 -3.565733e-01 -3.250463e-02 -7.699703e-02 -1.147480e-01
max 2.251473e+00 2.900592e+00 3.746715e+00 1.421225e+00 5.689749e+00 5.428686e+00 2.305484e+00 7.005517e+00 8.492691e+00 2.067813e+00 1.348928e+00 1.462243e+00 1.780477e+00 2.284613e+00 2.757707e+00 2.563498e+00 1.166693e+00 6.897670e+00 1.547064e+00 2.507700e+01 9.340057e+00 1.916184e+00 4.334017e+00 1.724910e+01 4.081033e+00 5.684162e+00 5.455161e+00 8.344942e+00 1.844173e+00 1.300693e+00 1.910123e+01 1.527089e+01 6.055347e+00 5.139274e+00 1.916476e+01 2.304566e+01 4.873195e+00 1.788011e+01 2.185199e+01 2.085339e+01 5.772376e+00 2.719201e+00 1.860495e+01 3.900707e+00 9.047155e+00 2.110437e+01 1.565763e+01 8.239075e+00 9.758484e+00 2.571922e+01 8.990645e+00 2.182055e+01 1.385289e+01 9.011996e+00 5.504021e+00 1.590785e+01 1.053265e+01 1.103256e+01 5.225949e+00 6.979831e+00 3.754550e+00 1.541325e+01 4.112015e+00 7.559137e+00 1.470925e+01 9.023294e+00 7.836227e+00 2.297213e+01 1.897604e+01 5.443982e+00 4.646625e+00 1.237655e+01 2.683078e+00 3.891313e+00 9.041457e+00 7.090983e+00 1.193609e+01 1.120690e+01 4.497448e+00 1.078466e+00 4.317003e+00 4.863209e+00 1.461834e+01 6.112844e+00 8.825044e+00 6.591100e+00 9.431601e+00 4.069708e+00 5.396371e+00 2.559505e+00 9.498073e+00 4.347517e+00 1.307164e+01 7.469290e+00 9.610873e+01 4.502032e+01 5.164423e-01 2.179636e+02 5.979094e+01 3.000429e+01 9.005730e+01 2.605935e+01 6.795546e+01 6.073391e+00 1.114929e+01 8.149612e+00 3.489049e+00 2.756839e+01 6.259724e+00 4.128642e+00 2.464859e+00 5.119815e+00 9.590300e-01 2.494700e+00 2.269548e+00 1.041408e+00 1.954836e+01 2.508153e+00 3.316472e+00 5.424127e+00 1.668428e+00 2.894438e+00 9.255967e+00 9.287854e+00 1.301673e+00 6.167860e+00 4.585841e+00 3.207356e+00 8.527772e+00 1.437697e+00 5.544573e+00 3.364441e+00 4.589505e+00 1.078263e+01 2.844192e+00 1.976108e+00 5.342552e+00 6.016952e+00 4.923038e+00 5.250088e+00 7.507115e+01 7.705610e+01 3.946119e+00 7.068781e+00 6.964969e+00 3.738761e+00 9.022162e+00 4.529243e+00 4.385644e+00 2.860425e+00 4.948706e+00 3.326894e+00 5.360816e+00 2.769028e+00 6.259724e+00 6.340324e+00 7.507115e+01 3.213943e+00 3.122398e+00 7.705610e+01 1.538556e+01 4.277496e+00 4.781417e+00 5.371512e+00 9.162211e+00 1.885086e+00 1.118546e+01 3.342324e+00 1.765939e+01 4.775519e+00 2.450878e+00 8.258188e+00 3.634516e+00 5.640248e-01 1.000263e+01 1.596920e+01 8.606444e+00 4.724086e-01 1.380468e+01 7.329736e+00 8.750714e+00 2.804472e+00 3.076485e+01 1.298751e+01 8.714745e+00

Come si vede, adesso tutti gli attributi sono stati standardizzati (hanno media nulla e varianza unitaria).
Ecco finalmente il training set finale pre elaborato e pronto per l'esecuzione dei modelli di machine learning:

In [ ]:
print(train_set_X.shape)
train_set_X.head()
(332564, 185)
Out[ ]:
Age_Band_of_Driver Age_of_Vehicle Engine_Capacity_.CC. Year Did_Police_Officer_Attend_Scene_of_Accident Latitude Longitude Number_of_Casualties Number_of_Vehicles Speed_limit Day_of_Week_sin Day_of_Week_cos Time_Interval_sin Time_Interval_cos Journey_Purpose_of_Driver_Commuting to/from work Journey_Purpose_of_Driver_Journey as part of work Journey_Purpose_of_Driver_Not known Journey_Purpose_of_Driver_Other Journey_Purpose_of_Driver_Other/Not known (2005-10) Journey_Purpose_of_Driver_Pupil riding to/from school Journey_Purpose_of_Driver_Taking pupil to/from school Junction_Location_Approaching junction or waiting/parked at junction approach Junction_Location_Cleared junction or waiting/parked at junction exit Junction_Location_Entering from slip road Junction_Location_Entering main road Junction_Location_Entering roundabout Junction_Location_Leaving main road Junction_Location_Leaving roundabout Junction_Location_Mid Junction - on roundabout or on main road Junction_Location_Not at or within 20 metres of junction make_ALFA ROMEO make_APRILIA make_AUDI make_BMW make_CHEVROLET make_CHRYSLER make_CITROEN make_DAEWOO make_DAIHATSU make_DUCATI make_FIAT make_FORD make_HARLEY-DAVIDSON make_HONDA make_HYUNDAI make_IVECO make_JAGUAR make_KAWASAKI make_KIA make_KTM make_LAND ROVER make_LEXUS make_LONDON TAXIS INT make_MAZDA make_MERCEDES make_MG make_MINI make_MITSUBISHI make_NISSAN make_OTHERS make_PEUGEOT make_PIAGGIO make_RENAULT make_ROVER make_SAAB make_SEAT make_SKODA make_SMART make_SUBARU make_SUZUKI make_TOYOTA make_TRIUMPH make_VAUXHALL make_VOLKSWAGEN make_VOLVO make_YAMAHA Vehicle_Manoeuvre_Changing lane to left Vehicle_Manoeuvre_Changing lane to right Vehicle_Manoeuvre_Going ahead left-hand bend Vehicle_Manoeuvre_Going ahead other Vehicle_Manoeuvre_Going ahead right-hand bend Vehicle_Manoeuvre_Moving off Vehicle_Manoeuvre_Overtaking - nearside Vehicle_Manoeuvre_Overtaking moving vehicle - offside Vehicle_Manoeuvre_Overtaking static vehicle - offside Vehicle_Manoeuvre_Parked Vehicle_Manoeuvre_Reversing Vehicle_Manoeuvre_Slowing or stopping Vehicle_Manoeuvre_Turning left Vehicle_Manoeuvre_Turning right Vehicle_Manoeuvre_U-turn Vehicle_Manoeuvre_Waiting to go - held up Vehicle_Manoeuvre_Waiting to turn left Vehicle_Manoeuvre_Waiting to turn right Vehicle_Type_Agricultural vehicle Vehicle_Type_Bus or coach (17 or more pass seats) Vehicle_Type_Car Vehicle_Type_Electric motorcycle Vehicle_Type_Goods 7.5 tonnes mgw and over Vehicle_Type_Goods over 3.5t. and under 7.5t Vehicle_Type_Goods vehicle - unknown weight Vehicle_Type_Minibus (8 - 16 passenger seats) Vehicle_Type_Motorcycle - unknown cc Vehicle_Type_Motorcycle 125cc and under Vehicle_Type_Motorcycle 50cc and under Vehicle_Type_Motorcycle over 125cc and up to 500cc Vehicle_Type_Motorcycle over 500cc Vehicle_Type_Other vehicle Vehicle_Type_Taxi/Private hire car Vehicle_Type_Van / Goods 3.5 tonnes mgw or under X1st_Point_of_Impact_Back X1st_Point_of_Impact_Did not impact X1st_Point_of_Impact_Front X1st_Point_of_Impact_Nearside X1st_Point_of_Impact_Offside 1st_Road_Class_A 1st_Road_Class_A(M) 1st_Road_Class_B 1st_Road_Class_C 1st_Road_Class_Motorway 1st_Road_Class_Unclassified Junction_Detail_Crossroads Junction_Detail_Mini-roundabout Junction_Detail_More than 4 arms (not roundabout) Junction_Detail_Not at junction or within 20 metres Junction_Detail_Other junction Junction_Detail_Private drive or entrance Junction_Detail_Roundabout Junction_Detail_Slip road Junction_Detail_T or staggered junction Local_Authority_(District)_0 Local_Authority_(District)_1 Local_Authority_(District)_10 Local_Authority_(District)_11 Local_Authority_(District)_12 Local_Authority_(District)_13 Local_Authority_(District)_14 Local_Authority_(District)_15 Local_Authority_(District)_16 Local_Authority_(District)_17 Local_Authority_(District)_18 Local_Authority_(District)_19 Local_Authority_(District)_2 Local_Authority_(District)_3 Local_Authority_(District)_4 Local_Authority_(District)_5 Local_Authority_(District)_6 Local_Authority_(District)_7 Local_Authority_(District)_8 Local_Authority_(District)_9 Local_Authority_(Highway)_0 Local_Authority_(Highway)_1 Local_Authority_(Highway)_10 Local_Authority_(Highway)_11 Local_Authority_(Highway)_12 Local_Authority_(Highway)_13 Local_Authority_(Highway)_14 Local_Authority_(Highway)_15 Local_Authority_(Highway)_16 Local_Authority_(Highway)_17 Local_Authority_(Highway)_18 Local_Authority_(Highway)_19 Local_Authority_(Highway)_2 Local_Authority_(Highway)_3 Local_Authority_(Highway)_4 Local_Authority_(Highway)_5 Local_Authority_(Highway)_6 Local_Authority_(Highway)_7 Local_Authority_(Highway)_8 Local_Authority_(Highway)_9 Road_Type_Dual carriageway Road_Type_One way street Road_Type_Roundabout Road_Type_Single carriageway Road_Type_Slip road Road_Type_Unknown Weather_Conditions_Fine + high winds Weather_Conditions_Fine no high winds Weather_Conditions_Fog or mist Weather_Conditions_Other Weather_Conditions_Raining + high winds Weather_Conditions_Raining no high winds Weather_Conditions_Snowing + high winds Weather_Conditions_Snowing no high winds Weather_Conditions_Unknown
0 0.576897 0.411860 0.058077 0.500637 -0.369934 0.294340 -0.594463 -0.568008 0.164129 -0.015721 1.073118 0.933574 1.731773 0.125566 -0.362620 2.563498 -0.857124 -0.144976 -0.646386 -0.039877 -0.107066 -0.521871 -0.230733 -0.057974 -0.245036 5.684162 -0.183313 -0.119833 -0.542248 -0.768821 -0.052353 -0.065484 -0.165143 -0.19458 -0.052179 -0.043392 -0.205204 -0.055928 -0.045762 -0.047954 -0.173239 -0.367755 -0.053749 -0.256364 -0.110532 -0.047384 -0.063867 -0.121373 -0.102475 -0.038881 -0.111227 -0.045828 -0.072187 -0.110963 -0.181685 -0.062862 10.532650 -0.090641 -0.191353 -0.14327 -0.266344 -0.064879 -0.243190 -0.13229 -0.067984 -0.110824 -0.127612 -0.043531 -0.052698 -0.183689 -0.215210 -0.080798 -0.372706 -0.256983 -0.110602 -0.141024 -0.08378 -0.089231 -0.222348 -0.927243 -0.231642 -0.205626 -0.068407 -0.16359 -0.113314 -0.15172 -0.106027 -0.245718 -0.185310 2.559505 -0.105285 -0.230016 -0.076502 -0.133882 -0.010405 -0.022212 0.516442 -0.004588 -0.016725 -0.033329 -0.011104 -0.038374 -0.014716 -0.164653 -0.089692 -0.122705 -0.286611 -0.036273 -0.159751 -0.24221 -0.405703 5.119815 -1.04272 -0.40085 -0.440616 1.041408 -0.051155 -0.3987 -0.301525 -0.184361 -0.599367 -0.345490 -0.108038 -0.107667 -0.768242 -0.162131 -0.218062 3.207356 -0.117264 -0.695557 -0.180357 -0.297226 -0.217888 -0.092742 -0.351594 -0.506045 -0.187176 -0.166197 -0.203127 -0.190473 -0.013321 -0.012978 -0.253414 -0.141467 -0.143576 -0.267468 -0.110838 -0.220787 -0.228017 2.860425 -0.202073 -0.300581 -0.186539 -0.361138 -0.159751 -0.157721 -0.013321 -0.311144 3.122398 -0.012978 -0.064996 -0.233782 -0.209143 -0.186167 -0.109144 -0.530480 -0.089402 -0.299193 -0.056627 -0.209401 -0.408017 -0.121092 3.634516 -1.772972 -0.099974 -0.062621 -0.116192 -2.116811 -0.072439 -0.136431 -0.114276 2.804472 -0.032505 -0.076997 -0.114748
1 0.576897 -0.266885 0.760006 -0.113088 2.659907 2.250818 -1.275829 -0.568008 0.164129 -0.710233 1.348928 -0.254336 -0.886720 -0.288093 -0.362620 -0.390092 1.166693 -0.144976 -0.646386 -0.039877 -0.107066 1.916184 -0.230733 -0.057974 -0.245036 -0.175927 -0.183313 -0.119833 -0.542248 -0.768821 -0.052353 -0.065484 -0.165143 -0.19458 -0.052179 -0.043392 -0.205204 -0.055928 -0.045762 -0.047954 -0.173239 -0.367755 -0.053749 -0.256364 -0.110532 -0.047384 -0.063867 -0.121373 -0.102475 -0.038881 -0.111227 -0.045828 -0.072187 -0.110963 -0.181685 -0.062862 -0.094943 -0.090641 -0.191353 -0.14327 -0.266344 -0.064879 4.112015 -0.13229 -0.067984 -0.110824 -0.127612 -0.043531 -0.052698 -0.183689 -0.215210 -0.080798 -0.372706 -0.256983 -0.110602 -0.141024 -0.08378 -0.089231 -0.222348 -0.927243 -0.231642 -0.205626 -0.068407 -0.16359 -0.113314 -0.15172 -0.106027 -0.245718 -0.185310 2.559505 -0.105285 -0.230016 -0.076502 -0.133882 -0.010405 -0.022212 0.516442 -0.004588 -0.016725 -0.033329 -0.011104 -0.038374 -0.014716 -0.164653 -0.089692 -0.122705 -0.286611 -0.036273 -0.159751 -0.24221 -0.405703 -0.195320 -1.04272 2.49470 -0.440616 -0.960238 -0.051155 -0.3987 3.316472 -0.184361 -0.599367 -0.345490 -0.108038 -0.107667 -0.768242 -0.162131 4.585841 -0.311783 -0.117264 -0.695557 -0.180357 -0.297226 -0.217888 -0.092742 -0.351594 -0.506045 -0.187176 6.016952 -0.203127 -0.190473 -0.013321 -0.012978 -0.253414 -0.141467 -0.143576 -0.267468 -0.110838 -0.220787 -0.228017 -0.349598 -0.202073 -0.300581 -0.186539 -0.361138 6.259724 -0.157721 -0.013321 -0.311144 -0.320267 -0.012978 -0.064996 -0.233782 -0.209143 -0.186167 -0.109144 -0.530480 -0.089402 -0.299193 -0.056627 -0.209401 -0.408017 -0.121092 -0.275140 0.564025 -0.099974 -0.062621 -0.116192 0.472409 -0.072439 -0.136431 -0.114276 -0.356573 -0.032505 -0.076997 -0.114748
2 1.135089 -0.945630 1.168880 -0.113088 -0.369934 0.776563 -0.954291 2.461402 0.164129 -0.710233 1.348928 -0.254336 1.588981 -0.288093 2.757707 -0.390092 -0.857124 -0.144976 -0.646386 -0.039877 -0.107066 -0.521871 -0.230733 -0.057974 -0.245036 -0.175927 -0.183313 -0.119833 1.844173 -0.768821 -0.052353 -0.065484 -0.165143 -0.19458 -0.052179 -0.043392 -0.205204 -0.055928 -0.045762 -0.047954 -0.173239 -0.367755 -0.053749 -0.256364 -0.110532 -0.047384 -0.063867 -0.121373 -0.102475 -0.038881 -0.111227 -0.045828 -0.072187 -0.110963 -0.181685 -0.062862 -0.094943 -0.090641 -0.191353 -0.14327 -0.266344 -0.064879 -0.243190 -0.13229 -0.067984 -0.110824 -0.127612 -0.043531 -0.052698 -0.183689 4.646625 -0.080798 -0.372706 -0.256983 -0.110602 -0.141024 -0.08378 -0.089231 -0.222348 -0.927243 -0.231642 -0.205626 -0.068407 -0.16359 -0.113314 -0.15172 -0.106027 -0.245718 -0.185310 2.559505 -0.105285 -0.230016 -0.076502 -0.133882 -0.010405 -0.022212 0.516442 -0.004588 -0.016725 -0.033329 -0.011104 -0.038374 -0.014716 -0.164653 -0.089692 -0.122705 -0.286611 -0.036273 -0.159751 -0.24221 -0.405703 -0.195320 0.95903 -0.40085 -0.440616 1.041408 -0.051155 -0.3987 -0.301525 -0.184361 -0.599367 2.894438 -0.108038 -0.107667 -0.768242 -0.162131 -0.218062 -0.311783 -0.117264 -0.695557 -0.180357 -0.297226 -0.217888 -0.092742 -0.351594 -0.506045 -0.187176 -0.166197 -0.203127 -0.190473 -0.013321 -0.012978 -0.253414 -0.141467 -0.143576 -0.267468 -0.110838 -0.220787 -0.228017 2.860425 -0.202073 -0.300581 -0.186539 -0.361138 -0.159751 -0.157721 -0.013321 -0.311144 -0.320267 -0.012978 -0.064996 -0.233782 -0.209143 -0.186167 -0.109144 -0.530480 -0.089402 3.342324 -0.056627 -0.209401 -0.408017 -0.121092 -0.275140 0.564025 -0.099974 -0.062621 -0.116192 0.472409 -0.072439 -0.136431 -0.114276 -0.356573 -0.032505 -0.076997 -0.114748
3 0.576897 -0.040637 1.093423 0.193774 -0.369934 -0.670391 0.854058 0.946697 0.164129 -0.710233 1.348928 -0.254336 1.780477 0.569476 2.757707 -0.390092 -0.857124 -0.144976 -0.646386 -0.039877 -0.107066 -0.521871 -0.230733 -0.057974 4.081033 -0.175927 -0.183313 -0.119833 -0.542248 -0.768821 -0.052353 -0.065484 -0.165143 -0.19458 -0.052179 -0.043392 -0.205204 -0.055928 -0.045762 -0.047954 -0.173239 -0.367755 -0.053749 -0.256364 -0.110532 -0.047384 -0.063867 -0.121373 -0.102475 -0.038881 -0.111227 -0.045828 -0.072187 -0.110963 -0.181685 -0.062862 -0.094943 -0.090641 -0.191353 -0.14327 -0.266344 -0.064879 4.112015 -0.13229 -0.067984 -0.110824 -0.127612 -0.043531 -0.052698 -0.183689 -0.215210 -0.080798 -0.372706 -0.256983 -0.110602 -0.141024 -0.08378 -0.089231 -0.222348 -0.927243 -0.231642 -0.205626 -0.068407 -0.16359 -0.113314 -0.15172 -0.106027 -0.245718 5.396371 -0.390701 -0.105285 -0.230016 -0.076502 -0.133882 -0.010405 -0.022212 0.516442 -0.004588 -0.016725 -0.033329 -0.011104 -0.038374 -0.014716 -0.164653 -0.089692 -0.122705 -0.286611 -0.036273 -0.159751 -0.24221 -0.405703 -0.195320 0.95903 -0.40085 -0.440616 1.041408 -0.051155 -0.3987 -0.301525 -0.184361 -0.599367 -0.345490 -0.108038 -0.107667 -0.768242 -0.162131 -0.218062 3.207356 -0.117264 -0.695557 -0.180357 -0.297226 -0.217888 -0.092742 -0.351594 1.976108 -0.187176 -0.166197 -0.203127 -0.190473 -0.013321 -0.012978 -0.253414 -0.141467 -0.143576 -0.267468 -0.110838 -0.220787 -0.228017 -0.349598 -0.202073 -0.300581 -0.186539 -0.361138 -0.159751 -0.157721 -0.013321 -0.311144 -0.320267 -0.012978 -0.064996 -0.233782 -0.209143 -0.186167 -0.109144 1.885086 -0.089402 -0.299193 -0.056627 -0.209401 -0.408017 -0.121092 -0.275140 0.564025 -0.099974 -0.062621 -0.116192 -2.116811 -0.072439 -0.136431 -0.114276 -0.356573 -0.032505 -0.076997 8.714745
4 -1.097680 -0.266885 0.707362 0.193774 -0.369934 1.578580 -0.094655 -0.568008 0.164129 -0.710233 1.348928 -0.254336 -0.363543 2.054828 -0.362620 -0.390092 1.166693 -0.144976 -0.646386 -0.039877 -0.107066 -0.521871 -0.230733 -0.057974 -0.245036 5.684162 -0.183313 -0.119833 -0.542248 -0.768821 -0.052353 -0.065484 -0.165143 -0.19458 -0.052179 -0.043392 -0.205204 -0.055928 -0.045762 -0.047954 -0.173239 -0.367755 -0.053749 -0.256364 -0.110532 -0.047384 -0.063867 -0.121373 -0.102475 -0.038881 -0.111227 -0.045828 -0.072187 -0.110963 -0.181685 -0.062862 -0.094943 -0.090641 -0.191353 -0.14327 -0.266344 -0.064879 -0.243190 -0.13229 -0.067984 -0.110824 -0.127612 -0.043531 -0.052698 -0.183689 -0.215210 -0.080798 -0.372706 3.891313 -0.110602 -0.141024 -0.08378 -0.089231 -0.222348 1.078466 -0.231642 -0.205626 -0.068407 -0.16359 -0.113314 -0.15172 -0.106027 -0.245718 -0.185310 -0.390701 -0.105285 -0.230016 -0.076502 -0.133882 -0.010405 -0.022212 0.516442 -0.004588 -0.016725 -0.033329 -0.011104 -0.038374 -0.014716 -0.164653 -0.089692 -0.122705 -0.286611 -0.036273 -0.159751 -0.24221 -0.405703 -0.195320 -1.04272 -0.40085 2.269548 1.041408 -0.051155 -0.3987 -0.301525 -0.184361 -0.599367 -0.345490 -0.108038 -0.107667 -0.768242 -0.162131 -0.218062 3.207356 -0.117264 -0.695557 -0.180357 -0.297226 -0.217888 -0.092742 -0.351594 -0.506045 -0.187176 -0.166197 -0.203127 -0.190473 -0.013321 -0.012978 -0.253414 -0.141467 -0.143576 -0.267468 -0.110838 4.529243 -0.228017 -0.349598 4.948706 -0.300581 -0.186539 -0.361138 -0.159751 -0.157721 -0.013321 -0.311144 -0.320267 -0.012978 -0.064996 -0.233782 -0.209143 -0.186167 -0.109144 -0.530480 -0.089402 -0.299193 -0.056627 -0.209401 -0.408017 -0.121092 3.634516 -1.772972 -0.099974 -0.062621 -0.116192 0.472409 -0.072439 -0.136431 -0.114276 -0.356573 -0.032505 -0.076997 -0.114748

4.4. Pipeline¶

Alla fine della fase di pre-processing, non resta che realizzare un'unica pipeline che riassuma tutte le fasi di processamento del dataset, così che in un solo colpo tali fasi è possibile applicarle sia al training set ma soprattutto al test set; in modo da rendere il test set compatibile con il train set.

Verranno create le classi WrapperTransformer e WrapperTransformer2 che serviranno per il wrapping dei trasformatori custom creati in precedenza; in modo da poter sfruttare le trasformazioni passando tra gli step della pipeline non solo il singolo dataset X ma anche la classe target y. In particolare la prima classe è stata realizzata per i transformer custom e la seconda per altri trasformatori quali ad esempio un ColumnTransformer:

In [ ]:
class WrapperTransformer(BaseEstimator, TransformerMixin):
    def __init__(self, transformer):
        self.transformer = transformer
        
    def transform(self,X,y=None):
        if isinstance(X, tuple):
            X, y = X
            X['Accident_Severity'] = y
            X2 = self.transformer.transform(X)
            yy = X2['Accident_Severity']
            X.drop('Accident_Severity', axis=1, inplace=True)
            X2.drop('Accident_Severity', axis=1, inplace=True)
            return X2, yy
        return self.transformer.transform(X)

    def fit(self, X, y=None):
        if isinstance(X, tuple):
            X, y = X
            X['Accident_Severity'] = y
            self.transformer.fit(X=X)
            X.drop('Accident_Severity', axis=1, inplace=True)
        else:
          self.transformer.fit(X=X)
        return self
In [ ]:
class WrapperTransformer2(BaseEstimator, TransformerMixin):
    def __init__(self, transformer):
        self.transformer = transformer
        
    def transform(self,X,y=None):
        ver=False
        if isinstance(X, tuple):
            X, y = X
            ver=True
        X2 = self.transformer.transform(X)
        if ver:
          return X2, y
        return X2

    def fit(self, X, y=None):
        if isinstance(X, tuple):
            X, y = X
        self.transformer.fit(X)
        return self

A questo punto non resta che costruire la pipeline. In particolare verranno costruite due pipeline: full_pipeline_train e full_pipeline_test.
Si sono dovute realizzare due pipeline perchè per quanto riguarda il processamento del training set, sono presenti due step che non sono necessari per il testing set, ovvero l'operazione di rimozione degli outliers e quella di undersampling per il problema della classe sbilanciata. A parte tale differenza, per il resto le pipeline sono divise in:

  1. data_cleaning_pipeline: è la pipeline che effettua tutte le operazione di cleaning sul dataset (in realtà sono due separate per train e test perchè all'interno di questa pipeline si effettua la rimozione degli outliers).
  2. data_transformation_pipeline: è la pipeline che effettua le operazioni di trasformazione, in particolare le operazioni per ridurre la dimensionalità di alcuni attributi (dimensionality_pipeline), per effettuare la codifica degli attributi categorici in numerici (encoding_pipeline) ed infine per effettuare lo scaling degli attributi (scaling_pipeline).

Da notare l'aspetto importante: anche se sono presenti due pipeline, si effettua lo stesso solamente la fit (o nel caso fit_transform) sul training set con la pipeline full_pipeline_train; e la transform sul testing set con la pipeline full_pipeline_test. Questo è possibile perchè alla fine le due pipeline hanno in comune il riferimento della pipeline per le transformazioni (data_transformation_pipeline); mentre per la data_cleaning_pipeline anche se non è in comune, esse non effettuano operazioni sulla fit ma solo sulla transform.

In [ ]:
colToDrop = ['Driver_IMD_Decile', 'model', '2nd_Road_Class', '2nd_Road_Number', 
    'LSOA_of_Accident_Location', 'Junction_Control', 'Date', 'Location_Easting_OSGR', 
    'Location_Northing_OSGR', 'Accident_Index', 'Vehicle_Reference', 'Police_Force',
    '1st_Road_Number']

featureSelection = ['Was_Vehicle_Left_Hand_Drive', 'InScotland',
    'Pedestrian_Crossing-Human_Control', 'Towing_and_Articulation', 'Urban_or_Rural_Area',
    'Vehicle_Location.Restricted_Lane', 'Carriageway_Hazards', 'Special_Conditions_at_Site',
    'Hit_Object_in_Carriageway', 'Sex_of_Driver', 'Skidding_and_Overturning',
    'Hit_Object_off_Carriageway', 'Propulsion_Code', 'Road_Surface_Conditions', 'Light_Conditions',
    'Driver_Home_Area_Type', 'Vehicle_Leaving_Carriageway', 'Pedestrian_Crossing-Physical_Facilities']

days = ['Monday', 'Tuesday',  'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
categoricalRemaind = ['Journey_Purpose_of_Driver', 'Junction_Location', 'make', 'Vehicle_Manoeuvre', 
    'Vehicle_Type', 'X1st_Point_of_Impact', '1st_Road_Class', 'Junction_Detail', 'Local_Authority_(District)', 
    'Local_Authority_(Highway)', 'Road_Type', 'Weather_Conditions']

col_outliers = ['Age_of_Vehicle', 'Engine_Capacity_.CC.', 'Number_of_Casualties', 'Number_of_Vehicles']

data_cleaning_pipeline_after = Pipeline([
        ('replaceMissingToNaN', ReplaceValuesTransformer("Data missing or out of range", np.NaN)),
        ('dropColumns', ColumnDropperTransformer(colToDrop)),
        ('dropRowsAnyNaN', RowsDropperTransformer()),
])

data_cleaning_pipeline_before = Pipeline([
        ('featureSelection', WrapperTransformer(ColumnDropperTransformer(featureSelection))),
        ('binaryProblem', FunctionTransformer(transformAccidentSverity, validate=False)),
])

data_cleaning_pipeline = Pipeline([
        ('data_cleaning_pipeline_after', data_cleaning_pipeline_after),
        ('dropRowsOutliers', RowsDropperOutliersTransformer(col_outliers)),
        ('data_cleaning_pipeline_before', data_cleaning_pipeline_before),
])

data_cleaning_pipeline_test = Pipeline([
        ('data_cleaning_pipeline_after', data_cleaning_pipeline_after),
        ('data_cleaning_pipeline_before', data_cleaning_pipeline_before),
])

dimensionality_pipeline = Pipeline([
    ('timeToTimeInterval', WrapperTransformer(TimeTransformer())),
    ('ageDriverDeleteValues', WrapperTransformer(RowsDropperTransformer('Age_Band_of_Driver'))),
    ('speedLimitDeleteValues', WrapperTransformer(RowsDropperTransformer('Speed_limit'))),
    ('localDistrictClustering', WrapperTransformer(ClusteringReplaceValuesTransformer("Local_Authority_(District)"))),
    ('localHighwayClustering', WrapperTransformer(ClusteringReplaceValuesTransformer("Local_Authority_(Highway)"))),
    ('makeOthers', WrapperTransformer(VehiclesFeatureGroupNameTransformer())),    
])

encoding_pipeline = Pipeline([
    ("dayWeekEncoder", WrapperTransformer(CircularEncoder(feature="Day_of_Week", cat=[days], number=7))),
    ("timeIntervalEncoder", WrapperTransformer(CircularEncoder())),
    ('colTransformer', WrapperTransformer2(ColumnTransformer([
       ("ageDriverEncoder", OrdinalEncoder(), ['Age_Band_of_Driver']),
        ("oneHotEncoderOthers", OneHotEncoder(sparse=False), categoricalRemaind),
    ],remainder='passthrough'))),
])


scaling_pipeline = Pipeline([
    ('standardScaler', WrapperTransformer2(StandardScaler())),
#     ('minMaxScaler', WrapperTransformer2(MinMaxScaler())),      
    #('robustScaler', WrapperTransformer2(RobustScaler())),
])

data_transformation_pipeline = Pipeline([
        ('dimensionality', dimensionality_pipeline),
        ('encoding', encoding_pipeline),
        ('scaling', scaling_pipeline),
])

full_pipeline_test = Pipeline([
        ('dataCleaning', data_cleaning_pipeline_test),
        ('dataTransformation', data_transformation_pipeline),
])

full_pipeline_train = Pipeline([
        ('dataCleaning', data_cleaning_pipeline),
        ('underSampling', FunctionTransformer(underSampling, validate=False)),
        ('dataTransformation', data_transformation_pipeline),
        
])

Per concludere questa sezione, si rendono i dataset di train e test belli e pronti per le fasi successive in cui verranno eseguiti i modelli di data mininig (da notare che il training set è già pronto perchè le trasformazioni sono state eseguite passo passo, però per una questione illustrativa verrà trasformato il training set iniziale senza trasformazioni con la pipeline):

In [ ]:
print("Prima training set pronto -->", train_set_X.shape)
print("Prima training set iniziale -->", train_set_iniziale.shape)
X_train, y_train = full_pipeline_train.fit_transform(train_set_iniziale)
print("Dopo X_train -->", X_train.shape)
print("Dopo y_train -->", y_train.shape)
Prima training set pronto --> (332564, 185)
Prima training set iniziale --> (2037823, 56)
Dopo X_train --> (332564, 185)
Dopo y_train --> (332564,)
In [ ]:
print("Prima testing set iniziale -->", test_set.shape)
X_test, y_test = full_pipeline_test.transform(test_set)
print("Dopo X_test -->", X_test.shape)
print("Dopo y_test -->", y_test.shape)
Prima testing set iniziale --> (20585, 56)
Dopo X_test --> (14325, 185)
Dopo y_test --> (14325,)

Ecco i dataset pronti train e test con le rispettive classi target:

In [ ]:
X_train, y_train
Out[ ]:
(array([[ 0.57689672, -0.36262012,  2.56349817, ...,  0.9335739 ,
          1.73177307,  0.12556571],
        [ 0.57689672, -0.36262012, -0.39009195, ..., -0.25433556,
         -0.88672032, -0.28809265],
        [ 1.1350888 ,  2.75770688, -0.39009195, ..., -0.25433556,
          1.58898073, -0.28809265],
        ...,
        [ 0.01870465, -0.36262012,  2.56349817, ..., -0.25433556,
          0.35113021, -1.1456611 ],
        [ 0.01870465, -0.36262012, -0.39009195, ...,  0.9335739 ,
         -0.01881193, -1.08721922],
        [ 1.1350888 , -0.36262012,  2.56349817, ...,  0.9335739 ,
          1.73177307,  0.12556571]]),
 0         0
 1         0
 2         0
 3         0
 4         0
          ..
 332573    1
 332574    1
 332575    1
 332576    1
 332577    1
 Name: Accident_Severity, Length: 332564, dtype: uint8)
In [ ]:
X_test, y_test
Out[ ]:
(array([[ 0.01870465, -0.36262012,  2.56349817, ..., -0.25433556,
         -0.88672032,  1.42704425],
        [-1.65587158, -0.36262012, -0.39009195, ...,  1.46224334,
         -1.07821646,  0.5694758 ],
        [ 0.01870465, -0.36262012,  2.56349817, ...,  1.46224334,
         -1.07821646,  0.5694758 ],
        ...,
        [ 0.57689672,  2.75770688, -0.39009195, ..., -0.25433556,
         -1.07821646,  0.5694758 ],
        [-0.53948743, -0.36262012,  2.56349817, ..., -0.25433556,
          0.35113021, -1.1456611 ],
        [ 0.57689672, -0.36262012,  2.56349817, ...,  0.9335739 ,
         -0.01881193, -1.08721922]]),
 1755336    0
 253404     0
 1924463    0
 286093     0
 2021059    0
           ..
 261461     0
 236224     0
 1065003    0
 356598     0
 1018300    0
 Name: Accident_Severity, Length: 14325, dtype: uint8)

5. Model Selection¶

In questa fase verranno addestrati più modelli per la classificazione, per trovare il modello che generalizzi meglio. Essi saranno valutati attraverso la tecnica KFold Cross Validation. Dove il numero di fold sarà pari a 3 per questioni di costo temporale dell'esecuzione.
Ogni modello sarà il migliore della loro rispettiva classe di ipotesi, perchè per ognuno verrà scelto il migliore sulla base della migliore combinazione degli iperparametri, attraverso l'ausilio della GridSearchCV.

Di seguito è descritta la funzione compute_performance che data la lista dei modelli scelti, ritorna un dizionario che, per ogni modello riporta i suoi valori di performance in termini di: accuracy, precision, recall, f1 e auc. Da notare che tali funzioni di score saranno calcolate su un validation set creato ad hoc.

Di seguito sono presenti le griglie adottate per la Grid Search per ogni modello. Dopo di ciò si addestreranno tali modelli e si sceglieranno i migliori iperparametri dei modelli sulla base del valore di accuracy (si ricorda che in questo caso il training set è bilanciato). I modelli calcolati saranno inseriti nella lista estimators.

In [ ]:
estimators=[]
In [ ]:
# Logistic Regression
grid_reg_log = {
    'C': [0.1, 1.0, 10, 100],
    'max_iter': [100, 200]
}

# Decision Tree
grid_dec_tree = {
    'max_depth': [10, 20],
    'min_samples_split': [5, 10],
    'min_samples_leaf': [2, 4],
}

# Bernoulli Naive Bayes
grid_BernoulliNB = {
    'alpha': [0.5, 1.0],
    'binarize': [0.5, 1.0],
}

#KNN
grid_KNN = {
    'n_neighbors': [3, 5, 7, 9],
    'weights': ['uniform', 'distance'],
    'p': [1, 2]
}

#Multi-layer perceptron
grid_MLP = {
    'hidden_layer_sizes': [(10,)],
    'activation': ['logistic', 'relu'],
    'solver': ['adam', 'lbfgs'],
    'alpha': [0.001, 0.01],
    'learning_rate': ['adaptive'],
}

#SGD Classifier
grid_SGD = {
    'loss': ['hinge', 'log'],
    'penalty': ['l2', 'l1'],
    'alpha': [0.001, 0.1, 1.0],
    'max_iter': [1000, 10000],
    'tol': [0.01]
}

# Random forest
grid_Random_forest = {
    'n_estimators': [50, 500],
    'max_depth': [10, 20],
    'min_samples_split': [5, 10],
    'min_samples_leaf': [2, 4]
}

# Ada Boost
grid_Ada_Boost = {
    'base_estimator__max_depth': [3, 5, 10],
    'n_estimators': [50, 100],
    'learning_rate': [0.1, 1, 2]
}

# XGBoost
grid_XGBoost = {
    'max_depth': [3, 5, 10],
    'n_estimators': [50, 100],
    'learning_rate': [0.1, 1, 2],
    'subsample': [0.5, 0.9],
    'colsample_bytree': [0.5],
    'gamma': [0, 1, 5]
}

5.1. Logistic Regression:¶

In [ ]:
# Logistic Regression
filename = './models/log_reg_cv.sav'

# clf = LogisticRegression()      
# grid_search = GridSearchCV(
#                 estimator=clf,
#                 param_grid=grid_log_reg,
#                 cv=3, 
#                 scoring="accuracy",
#                 n_jobs=-1
# )
# grid_result = grid_search.fit(X_train, y_train)
# logreg_cv = grid_search.best_estimator_
# print(grid_search.best_estimator_)

# save the model to disk
# pickle.dump(logreg_cv, open(filename, 'wb'))

#load the model from disk
logreg_cv = pickle.load(open(filename, 'rb'))
estimators.append(logreg_cv)

5.2. Decision Tree:¶

In [ ]:
# Decision Tree
filename = './models/dec_tree_cv.sav'

# clf = DecisionTreeClassifier()     
# grid_search = GridSearchCV(
#                 estimator=clf,
#                 param_grid=grid_dec_tree,
#                 cv=3, 
#                 scoring="accuracy",
#                 n_jobs=-1
# )
# grid_result = grid_search.fit(X_train, y_train)
# dec_tree_cv = grid_search.best_estimator_
# print(grid_search.best_estimator_)

# save the model to disk
# pickle.dump(dec_tree_cv, open(filename, 'wb'))

#load the model from disk
dec_tree_cv = pickle.load(open(filename, 'rb'))
estimators.append(dec_tree_cv)

5.3. Bernoulli Naive Bayes:¶

In [ ]:
# Bernoulli Naive Bayes
filename = './models/bernoulliNB_cv.sav'

# clf = BernoulliNB()
# grid_search = GridSearchCV(
#                 estimator=clf,
#                 param_grid=grid_BernoulliNB,
#                 cv=3, 
#                 scoring="accuracy",
#                 n_jobs=-1
# )
# grid_search.fit(X_train, y_train)
# bernoulli_NB_cv = grid_search.best_estimator_
# print(grid_search.best_estimator_)

# save the model to disk
# pickle.dump(bernoulli_NB_cv, open(filename, 'wb'))

#load the model from disk
bernoulli_NB_cv = pickle.load(open(filename, 'rb'))
estimators.append(bernoulli_NB_cv)

5.4. K Neighbors Classifier:¶

In [ ]:
# KNN
filename = './models/KNN_cv.sav'

# clf = KNeighborsClassifier()
# grid_search = GridSearchCV(
#                 estimator=clf,
#                 param_grid=grid_KNN,
#                 cv=3, 
#                 scoring="accuracy",
#                 n_jobs=-1
# )
# grid_search.fit(X_train, y_train)
# KNN_cv = grid_search.best_estimator_
# print(grid_search.best_estimator_)

# save the model to disk
# pickle.dump(KNN_cv, open(filename, 'wb'))

#load the model from disk
KNN_cv = pickle.load(open(filename, 'rb'))
estimators.append(KNN_cv)

5.5. Multi-layer perceptron:¶

In [ ]:
#Multi-layer perceptron
filename = './models/MLPClassifier_cv.sav'

# clf = MLPClassifier()
# grid_search = GridSearchCV(
#                 estimator=clf,
#                 param_grid=grid_MLP,
#                 cv=3, 
#                 scoring="accuracy",
#                 n_jobs=-1
# )
# grid_search.fit(X_train, y_train)
# mlp_cv = grid_search.best_estimator_
# print(grid_search.best_estimator_)

# save the model to disk
# pickle.dump(mlp_cv, open(filename, 'wb'))

#load the model from disk
mlp_cv = pickle.load(open(filename, 'rb'))
estimators.append(mlp_cv)

5.6. SGD Classifier:¶

In [ ]:
#SGD Classifier
filename = './models/SGD_cv.sav'

# clf = SGDClassifier()
# grid_search = GridSearchCV(
#                 estimator=clf,
#                 param_grid=grid_SGD,
#                 cv=3, 
#                 scoring="accuracy",
#                 n_jobs=-1
# )
# grid_search.fit(X_train, y_train)
# sgd_cv = grid_search.best_estimator_
# print(grid_search.best_estimator_)

# save the model to disk
# pickle.dump(sgd_cv, open(filename, 'wb'))

#load the model from disk
sgd_cv = pickle.load(open(filename, 'rb'))
estimators.append(sgd_cv)

5.7. Random Forest:¶

In [ ]:
# Random forest
filename = './models/Random_forest_cv.sav'

# clf = RandomForestClassifier()
# grid_search = GridSearchCV(
#                 estimator=clf,
#                 param_grid=grid_Random_forest,
#                 cv=3, 
#                 scoring="accuracy",
#                 n_jobs=-1
# )
# grid_search.fit(X_train, y_train)
# rf_cv = grid_search.best_estimator_
# print(grid_search.best_estimator_)

# save the model to disk
# pickle.dump(rf_cv, open(filename, 'wb'))

#load the model from disk
rf_cv = pickle.load(open(filename, 'rb'))
estimators.append(rf_cv)

5.8. Ada Boost:¶

In [ ]:
# Ada Boost
filename = './models/Ada_boost_cv.sav'

# clf = AdaBoostClassifier()
# grid_search = GridSearchCV(
#                 estimator=clf,
#                 param_grid=grid_Ada_Boost,
#                 cv=3, 
#                 scoring="accuracy",
#                 n_jobs=-1
# )
# grid_search.fit(X_train, y_train)
# adaBoost_cv = grid_search.best_estimator_
# print(grid_search.best_estimator_)

# save the model to disk
# pickle.dump(adaBoost_cv, open(filename, 'wb'))

#load the model from disk
adaBoost_cv = pickle.load(open(filename, 'rb'))
estimators.append(adaBoost_cv)

5.9. XGBoost:¶

In [ ]:
# XGBoost
filename = './models/XGboost_cv.sav'

# clf = xgb.XGBClassifier()
# grid_search = GridSearchCV(
#                 estimator=clf,
#                 param_grid=grid_XGBoost,
#                 cv=3, 
#                 scoring="accuracy",
#                 n_jobs=-1
# )
# grid_search.fit(X_train, y_train)
# XGBoost_cv = grid_search.best_estimator_
# print(grid_search.best_estimator_)

# save the model to disk
# pickle.dump(XGBoost_cv, open(filename, 'wb'))

#load the model from disk
XGBoost_cv = pickle.load(open(filename, 'rb'))
estimators.append(XGBoost_cv)

Nella lista estimators sono presenti i modelli migliori per la loro rispettiva classe di ipotesi, calcolati mediante GridSearch su cross validation per la scelta dei migliori iperparametri. Adesso per valutare quale sia il migliore tra di essi, si farà uso di un validation set diviso dal training set, e si effetturà la valutazione mediante la funzione compute_performance che data la lista dei modelli scelti, restituisce un dizionario che, per ogni modello riporta i suoi valori di performance in termini di: accuracy, precision, recall, f1 e auc.

In [ ]:
X_train2, X_validation, y_train2, y_validation = train_test_split(X_train, y_train, test_size=0.2, random_state=42)
X_train2.shape, X_validation.shape, y_train2.shape, y_validation.shape
Out[ ]:
((266051, 185), (66513, 185), (266051,), (66513,))
In [ ]:
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from sklearn.metrics import f1_score
from sklearn.metrics import roc_auc_score
from sklearn.metrics import accuracy_score

def compute_performance(estimators, X_validation=X_validation, y_validation=y_validation):
    score_dict = {}
    for e in estimators:
        score_dict[e.__class__.__name__] = {}
        accuracy = accuracy_score(y_validation, e.predict(X_validation))
        score_dict[e.__class__.__name__]['accuracy'] = accuracy
        precision = precision_score(y_validation, e.predict(X_validation))
        score_dict[e.__class__.__name__]['precision'] = precision
        recall = recall_score(y_validation, e.predict(X_validation))
        score_dict[e.__class__.__name__]['recall'] = recall
        f1 = f1_score(y_validation, e.predict(X_validation))
        score_dict[e.__class__.__name__]['f1'] = f1
        auc = roc_auc_score(y_validation, e.predict(X_validation))
        score_dict[e.__class__.__name__]['auc'] = auc
    
    return score_dict
In [ ]:
estimators
Out[ ]:
[LogisticRegression(max_iter=200, solver='liblinear'),
 DecisionTreeClassifier(criterion='entropy', max_depth=20, min_samples_leaf=2,
                        min_samples_split=5, splitter='random'),
 BernoulliNB(alpha=0.5, binarize=0.5),
 KNeighborsClassifier(p=1, weights='distance'),
 MLPClassifier(alpha=0.01, hidden_layer_sizes=(10,), learning_rate='adaptive'),
 SGDClassifier(alpha=0.1, tol=0.01),
 RandomForestClassifier(criterion='entropy', max_depth=20, max_features='sqrt',
                        min_samples_leaf=2, min_samples_split=5,
                        n_estimators=50),
 AdaBoostClassifier(base_estimator=DecisionTreeClassifier(max_depth=5),
                    learning_rate=0.5)]
In [ ]:
filename = './score/score.csv'

# score_dict = compute_performance(estimators)
# # save to disk
# pickle.dump(score_dict, open(filename, 'wb'))

# load the model from disk
score_dict = pickle.load(open(filename, 'rb'))

df_score = pd.DataFrame(data=score_dict)
df_score
Out[ ]:
LogisticRegression DecisionTreeClassifier BernoulliNB KNeighborsClassifier MLPClassifier SGDClassifier RandomForestClassifier AdaBoostClassifier XGBClassifier
accuracy 0.691551 0.753472 0.662037 0.675612 0.776042 0.646123 0.770544 0.773438 0.775752
precision 0.699158 0.809760 0.681582 0.668640 0.861912 0.671886 0.849663 0.845647 0.845540
recall 0.672454 0.662616 0.608218 0.701623 0.657407 0.571181 0.657407 0.668981 0.674769
f1 0.685546 0.728835 0.642813 0.684735 0.745896 0.617454 0.741272 0.747011 0.750563
auc 0.691551 0.753472 0.662037 0.644735 0.776042 0.646123 0.770544 0.773438 0.775752
In [ ]:
ax = df_score.plot(kind='bar', rot=0, figsize=(20,10))

ax.set_ylabel('Metriche di performance')
ax.set_xlabel('classificatori')
ax.set_title('Performance dei classificatori')

for p in ax.containers:
    ax.bar_label(p, label_type="edge", labels=[f"{v:.1%}" for v in p.datavalues], fontsize=12)

plt.show()

6. Post processing¶

Le prestazioni appena valutate, in generale non sono molto soddisfacenti. Dopo molte modifiche iterative in tutte le fasi descritte in precedenza, si è arrivati ad una conclusione: cambiare l'algoritmo di undersampling per il bilanciamento delle classi.
L'idea è quella di provare con un algortimo di undersampling diverso dal RandomUnderSampling. In particolare l'algoritmo che si adotterà sarà il Near-Miss.

L'algoritmo di undersampling Near-Miss presenta 3 diverse varianti. La variante che verrà utilizzata sarà la variante 1 (Near-Miss v1).
NearMiss-1 seleziona i campioni positivi (campioni da sottocampionare) per i quali la distanza media dagli N campioni più vicini della classe negativa (classe minoritaria) è la più piccola.
Nel caso in figura verrà eliminato il punto + in basso:

Descrizione

Per maggiori dettagli e approfondimenti si riporta al seguente link: https://imbalanced-learn.org/stable/under_sampling.html

A questo punto di seguito si riporta il nuovo metodo di undersampling che verrà richiamato all'interno di un FunctionTransformer nella nuova pipeline:

In [ ]:
def underSampling_nearMiss(df):
    X, y = df
    rus = NearMiss(version=1, n_neighbors=3)
    X, y  = rus.fit_resample(X, y)
    return X, y

Si modifica quindi la pipeline per l'elaborazione del training set (full_pipeline_train), aggiornando il metodo di undersampling. Da notare che quest'ultimo lavora solamente con input numerici, per tale ragione si è deciso di inseriro a monte della pipeline di trasformazione dataTransformation:

In [ ]:
full_pipeline_train = Pipeline([
        ('dataCleaning', data_cleaning_pipeline),
        ('dataTransformation', data_transformation_pipeline), 
        ('underSampling_nearMiss', FunctionTransformer(underSampling_nearMiss, validate=False)),   
])

Si ricostruiscono il nuovo training set e tasting set:

In [ ]:
filename1 = './set/X_train_post_processing.sav'
filename2 = './set/y_train_post_processing.sav'

print("Prima training set iniziale -->", train_set_iniziale.shape)

#X_train, y_train = full_pipeline_train.fit_transform(train_set_iniziale)
#load the training set from disk
X_train = pickle.load(open(filename1, 'rb'))
y_train = pickle.load(open(filename2, 'rb'))

print("Dopo X_train -->", X_train.shape)
print("Dopo y_train -->", y_train.shape)
Prima training set iniziale --> (2037823, 56)
Dopo X_train --> (332560, 188)
Dopo y_train --> (332560,)
In [ ]:
filename1 = './set/X_test_post_processing.sav'
filename2 = './set/y_test_post_processing.sav'

print("Prima testing set iniziale -->", test_set.shape)

#X_test, y_test = full_pipeline_test.transform(test_set)
#load the training set from disk
X_test = pickle.load(open(filename1, 'rb'))
y_test = pickle.load(open(filename2, 'rb'))

print("Dopo X_test -->", X_test.shape)
print("Dopo y_test -->", y_test.shape)
Prima testing set iniziale --> (20585, 56)
Dopo X_test --> (14325, 188)
Dopo y_test --> (14325,)

A questo punto non resta che riaddetrare i modelli, ripetendo esattamente i passaggi fatti in precedenza. Da notare che in questo caso si dovrebbero ricalcolare i migliori iperparametri per ogni modello siccome è cambiato totalmente il training set, invece, consapevolmente si è deciso di utilizzare gli stessi iperparametri calcolati nei passaggi precedenti per motivi di velocità di esecuzione.

In [ ]:
estimators2=[]

6.1. Logistic Regression:¶

In [ ]:
# Logistic Regression
filename = './models_post_processing/log_reg_cv.sav'

# logreg_cv = logreg_cv.fit(X_train, y_train)

# save the model to disk
# pickle.dump(logreg_cv, open(filename, 'wb'))

#load the model from disk
logreg_cv = pickle.load(open(filename, 'rb'))
estimators2.append(logreg_cv)

6.2. Decision Tree:¶

In [ ]:
# Decision Tree
filename = './models_post_processing/dec_tree_cv.sav'

# dec_tree_cv = dec_tree_cv.fit(X_train, y_train)

# save the model to disk
# pickle.dump(dec_tree_cv, open(filename, 'wb'))

#load the model from disk
dec_tree_cv = pickle.load(open(filename, 'rb'))
estimators2.append(dec_tree_cv)

6.3. Bernoulli Naive Bayes:¶

In [ ]:
# Bernoulli Naive Bayes
filename = './models_post_processing/bernoulliNB_cv.sav'

# bernoulli_NB_cv = bernoulli_NB_cv.fit(X_train, y_train)

# save the model to disk
# pickle.dump(bernoulli_NB_cv, open(filename, 'wb'))

#load the model from disk
bernoulli_NB_cv = pickle.load(open(filename, 'rb'))
estimators2.append(bernoulli_NB_cv)

6.4. K Neighbors Classifier:¶

In [ ]:
# KNN
filename = './models_post_processing/KNN_cv.sav'

# KNN_cv = KNN_cv.fit(X_train, y_train)

# save the model to disk
# pickle.dump(KNN_cv, open(filename, 'wb'))

#load the model from disk
KNN_cv = pickle.load(open(filename, 'rb'))
estimators2.append(KNN_cv)

6.5. Multi-layer perceptron:¶

In [ ]:
#Multi-layer perceptron
filename = './models_post_processing/MLPClassifier_cv.sav'

# mlp_cv = mlp_cv.fit(X_train, y_train)

# save the model to disk
# pickle.dump(mlp_cv, open(filename, 'wb'))

#load the model from disk
mlp_cv = pickle.load(open(filename, 'rb'))
estimators2.append(mlp_cv)

6.6. SGD Classifier:¶

In [ ]:
#SGD Classifier
filename = './models_post_processing/SGD_cv.sav'

# sgd_cv = sgd_cv.fit(X_train, y_train)

# save the model to disk
# pickle.dump(sgd_cv, open(filename, 'wb'))

#load the model from disk
sgd_cv = pickle.load(open(filename, 'rb'))
estimators2.append(sgd_cv)

6.7. Random Forest:¶

In [ ]:
# Random forest
filename = './models_post_processing/Random_forest_cv.sav'

# rf_cv = rf_cv.fit(X_train, y_train)

# save the model to disk
# pickle.dump(rf_cv, open(filename, 'wb'))

#load the model from disk
rf_cv = pickle.load(open(filename, 'rb'))
estimators2.append(rf_cv)

6.8. Ada Boost:¶

In [ ]:
# Ada Boost
filename = './models_post_processing/Ada_boost_cv.sav'

# adaBoost_cv = adaBoost_cv.fit(X_train, y_train)

# save the model to disk
# pickle.dump(adaBoost_cv, open(filename, 'wb'))

#load the model from disk
adaBoost_cv = pickle.load(open(filename, 'rb'))
estimators2.append(adaBoost_cv)

6.9. XGBoost:¶

In [ ]:
# XGBoost
filename = './models_post_processing/XGboost_cv.sav'

# XGBoost_cv = XGBoost_cv.fit(X_train, y_train)

# save the model to disk
# pickle.dump(XGBoost_cv, open(filename, 'wb'))

#load the model from disk
XGBoost_cv = pickle.load(open(filename, 'rb'))
estimators2.append(XGBoost_cv)
In [ ]:
estimators2
Out[ ]:
[LogisticRegression(max_iter=200, solver='liblinear'),
 DecisionTreeClassifier(criterion='entropy', max_depth=20, min_samples_leaf=2,
                        min_samples_split=5, splitter='random'),
 BernoulliNB(alpha=0.5, binarize=0.5),
 KNeighborsClassifier(p=1, weights='distance'),
 MLPClassifier(alpha=0.01, hidden_layer_sizes=(10,), learning_rate='adaptive'),
 SGDClassifier(alpha=0.1, tol=0.01),
 RandomForestClassifier(criterion='entropy', max_depth=20, max_features='sqrt',
                        min_samples_leaf=2, min_samples_split=5,
                        n_estimators=50),
 AdaBoostClassifier(base_estimator=DecisionTreeClassifier(max_depth=5),
                    learning_rate=0.5)]

Anche in questo caso si farà uso di un validation set diviso dal training set, e si effetturà la valutazione mediante la funzione compute_performance.

In [ ]:
X_train2, X_validation, y_train2, y_validation = train_test_split(X_train, y_train, test_size=0.2, random_state=42)
X_train2.shape, X_validation.shape, y_train2.shape, y_validation.shape
Out[ ]:
((266048, 188), (66512, 188), (266048,), (66512,))
In [ ]:
filename = './score/score2.csv'

# score_dict2 = compute_performance(estimators2)
# # save to disk
# pickle.dump(score_dict2, open(filename, 'wb'))

# load the model from disk
score_dict2 = pickle.load(open(filename, 'rb'))

df_score2 = pd.DataFrame(data=score_dict2)
df_score2
Out[ ]:
BernoulliNB DecisionTreeClassifier LogisticRegression MLPClassifier SGDClassifier RandomForestClassifier AdaBoostClassifier KNeighborsClassifier XGBClassifier
accuracy 0.737521 0.817477 0.779258 0.866941 0.726455 0.868370 0.865483 0.725612 0.863198
precision 0.767884 0.898759 0.807648 0.924287 0.821933 0.939318 0.916427 0.778640 0.917599
recall 0.681459 0.715891 0.733588 0.799597 0.578734 0.787850 0.804555 0.691623 0.798305
f1 0.722095 0.796970 0.768838 0.857433 0.679220 0.856942 0.856855 0.732556 0.853805
auc 0.737567 0.817559 0.779295 0.866996 0.726575 0.868435 0.865532 0.692556 0.863250

Si plottano i risultati ottenuti:

In [ ]:
ax = df_score2.plot(kind='bar', rot=0, figsize=(20,10))

ax.set_ylabel('Metriche di performance')
ax.set_xlabel('classificatori')
ax.set_title('Performance dei classificatori')
plt.legend(loc="lower center")

for p in ax.containers:
    ax.bar_label(p, label_type="edge", labels=[f"{v:.1%}" for v in p.datavalues], fontsize=12)

plt.show()

Come si può vedere, i risultati emersi sono migliori di quelli in precedenza. A questo punto l'ultimo passo che rimane, è quello di selzionare il classificatore che performa meglio sul validation set in termini di accuracy, dato che il validation set è bilanciato, è valutare le performance definitive sui dati reali che il classificatore non ha mai visto, ovvero il test set iniziale.

In [ ]:
first_row = df_score2.iloc[0]
max_idx = first_row.idxmax()
print(max_idx)
max_col_name = df_score2[max_idx]
print(max_col_name)
max_value = first_row[max_idx]
print("Il valore massimo di accuracy è", max_value, "dato dal classificatore", max_idx)
RandomForestClassifier
accuracy     0.868370
precision    0.939318
recall       0.787850
f1           0.856942
auc          0.868435
Name: RandomForestClassifier, dtype: float64
Il valore massimo di accuracy è 0.8683696175126293 dato dal classificatore RandomForestClassifier
In [ ]:
best_estimator = estimators2[6]
best_estimator
Out[ ]:
RandomForestClassifier(criterion='entropy', max_depth=20, max_features='sqrt',
                       min_samples_leaf=2, min_samples_split=5,
                       n_estimators=50)

best_estimator corrisponde al classificatore con il valore più alto di accuracy.

7. Valutazioni delle performance¶

A questo punto non resta da valutare le performance finali del migliore classificatore scelto nella fase di model selction, con i dati presenti nel test set pre elaborati dalla pipeline per il test set. Per fare ciò si farà uso ancora una volta del metodo compute_performance, passando in ingresso non il validation set bensì il test set. Si farà la stessa cosa con il training set, in modo tale anche da valutare se il modello presenta un elevato livello di overfitting.

In [ ]:
filename = './score/score_test.csv'

# score_dict_test = compute_performance([best_estimator], X_test, y_test)
# # save to disk
# pickle.dump(score_dict_test, open(filename, 'wb'))

# load the model from disk
score_dict_test = pickle.load(open(filename, 'rb'))

df_score_test = pd.DataFrame(data=score_dict_test)
df_score_test.rename(columns={best_estimator.__class__.__name__: 'Test Set'}, inplace=True)
df_score_test
Out[ ]:
Test Set
accuracy 0.837095
auc 0.837095
f1 0.824117
precision 0.895451
recall 0.763310
In [ ]:
filename = './score/score_train.csv'

# score_dict_train = compute_performance([best_estimator], X_train, y_train)
# # save to disk
# pickle.dump(score_dict_train, open(filename, 'wb'))

# load the model from disk
score_dict_train = pickle.load(open(filename, 'rb'))

df_score_train = pd.DataFrame(data=score_dict_train)
df_score_train.rename(columns={best_estimator.__class__.__name__: 'Train set'}, inplace=True)
df_score_train
Out[ ]:
Train set
accuracy 0.869942
auc 0.869942
f1 0.858618
precision 0.940506
recall 0.789848
In [ ]:
df_merged = pd.concat([df_score_test, df_score_train], axis=1)
ax = df_merged.plot(kind='bar', rot=0)

ax.set_ylabel('Metriche di performance')
ax.set_xlabel('classificatori')
ax.set_title('Performance dei classificatori')
plt.legend(loc="lower center")

for p in ax.containers:
    ax.bar_label(p, label_type="edge", labels=[f"{v:.1%}" for v in p.datavalues], fontsize=12)

plt.show()

I risultati mostrano il valore di accuracy sul test set pari a 83,7%, mentre sul training set pari a 87,0%, da ciò si evince che non è presente overfitting. Inoltre anche le altre misure calcolate sul test set non si discostano molto dalle performance sul training set.
Da notare che il valore di recall pari a 76,3% è più basso rispetto al valore di precision pari a 89,5%: ciò mette in evidenza che il modello ottenuto è più preciso e meno sensibile, significa che quando il modello predice che un incidente è grave, ha una maggiore probabilità di essere corretto rispetto a quando predice che un incidente sia lieve.
Tuttavia, ciò può anche significare che il modello rischia di perdere alcuni casi di incidenti gravi, classificandoli erroneamente come lievi.
In generale, dipende dal contesto e dello scopo del modello: ad esempio se l'obiettivo è quello di rilevare in tempo reale in base alle features dell'incidente se questo è grave o lieve, in modo da avvisare i soccorsi con diversi livelli di priorità di intervento sul posto.
Se il contesto quindi è quello appena descritto, e l'obiettivo è quello di minimizzare le risorse (soccorsi), in modo da evitare la loro saturazione, limitando il loro intervento su altri incidenti; allora il modello si comporterebbe bene perchè avendo un valore di precision più elevato del valore di recall, si avrà una maggiore probabilità che gli incidenti lievi verranno classificati come gravi (quindi evitare di occupare risorse inutilmente dato che in realtà l'incidente è lieve).
In conclusione i risultati ottenuti sono soddisfacenti.

8. Conclusioni¶

Questo task di data mining ha visto una prima parte abbastanza corposa sull'analisi dei dati e sulla loro visualizzazione, ed una seconda parte di predizione dell'attributo target preceduto da tutta la fase di preprocessing dei dati. Inoltre si è visto come, tornando indietro nel ciclo di elaborazione, si sono ottenuti migliori risultati; infatti la fase successiva alla model selection è stata quella di rieseguire gli algoritmi di data mining, applicando ai dati una differente tecnica di undersampling per le classi sbilanciate. In definitiva si è scelto il miglior algoritmo sulla base di un validation set, e si sono calcolate le performance sul test set, tenuto in disparte dall'inizio dell'elaborazione del task.
Un'ultima precisazione va fatta, il trade-off tra qualità della soluzione e tempo di esecuzione è molto importante. Avendo un dataset con una dimensione abbastanza considerevole, non tutti gli algoritmi avevano un basso tempo di esecuzione, soprattutto se all'interno di una GridSearch (anche per tale ragione si sono utilizzati pochi parametri). Inoltre, anche la fase di preprocessing attraverso i diversi step racchiusi nelle pipeline erano abbastanza lenti. Per tali motivazioni, si è deciso di eseguire gli algoritmi una sola volta e salvare i risultati su disco, così che nella successiva esecuazione essi potranno essere caricati direttamente da disco, riducendo drasticamente i tempi di esecuzione.

Per concludere, l'output finale di tale task può essere la seguente pipeline: full_pipeline prende in input un qualsiasi dato nella forma iniziale non pre elaborato, e restituisce la predizione effettuata sull'attributo target Accident_Severity.

In [ ]:
full_pipeline = Pipeline([
        ('pre-processing', full_pipeline_test),
        ('classifier', best_estimator),
])